Skip to content

feat(rspress): add Rspress documentation site with PR preview CI#5415

Draft
kduret wants to merge 32 commits into
stagingfrom
test-rspress
Draft

feat(rspress): add Rspress documentation site with PR preview CI#5415
kduret wants to merge 32 commits into
stagingfrom
test-rspress

Conversation

@kduret

@kduret kduret commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a parallel Rspress (v2) documentation site under rspress/, mirroring docs.centreon.com, without changing the current Docusaurus production site.

  • Content migrated: versioned OnPrem docs (25.10 + 26.10, EN/FR), Cloud, Monitoring Connectors, Experience Monitoring, Log Management.
  • UI parity: Centreon branding (logo, colors, Red Hat Display + Esphimere fonts), product-aware navbar, product switcher, responsive burger menu, custom homepage, global footer, github-light/dracula code themes, image zoom.
  • Bundle optimization: build-time PNG compression + GIF/video re-encoding → deployed transfer 154 MB → 87 MB gzipped (-43%).
  • CI: Cloudflare Pages preview deployment per PR (free, scales to many concurrent PRs). The search index is trimmed on preview builds (code blocks excluded) to stay under Cloudflare's 25 MiB/file limit; staging/prod keep the full search index.

Requires a maintainer to set the Cloudflare secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID for previews to run.

Reviewed with the automated code + security review (no blocking issues; accessibility, supply-chain and hardening findings addressed).

Target version (i.e. version that this PR changes)

  • 24.10.x
  • 25.10.x
  • 26.10.x
  • Cloud
  • Monitoring Connectors
  • Experience Monitoring
  • Log Management

kduret added 25 commits May 22, 2026 10:50
Sets up a rspress 2.0.0-beta.21 site under rspress/ with EN/FR locales and
26.10/25.10 versioning. Migrates the full getting-started section (84 files)
for all 4 lang×version combos, with tabs, homepage components, and proper
sidebar configuration.
Extend migration from getting-started-only POC to all 22 sections (~325 files
per lang/version combo). Key additions:
- migrate.py: recursive migration of all .md/.mdx source files + full assets
- Sidebar auto-generated from versioned_sidebars JSON + FR label translations
- rspress.config.ts now imports sidebar from src/sidebar.ts
- transform(): fix indented code fences, normalize languages (SQL→text,
  XML→text, phpconf/apacheconf→bash), strip @site/ imports, remove HTML
  comments in MDX, decode %20 in asset paths
- Asset files with spaces renamed to hyphens to satisfy rspack resolver
Replace auto-migrated placeholder index.md with proper HomePage component
pages mirroring the 26.10 homepage but with /25.10/ links. Also guard
migrate.py against overwriting hand-crafted version index files.
In rspress v2 multiVersion, replaceVersion() only strips the version prefix
from the current URL when current != default. If sidebar links use /26.10/...
and the user switches to 25.10, the result is /25.10/26.10/... (double prefix).

Fix: default version (26.10) sidebar links and homepage/nav links now use
no version prefix (/getting-started/welcome instead of /26.10/...).
Sidebar key changes from '/26.10/' to '/' and '/fr/26.10/' to '/fr/'.
- New custom theme (theme/index.tsx) with URL-driven VersionSelector and
  LanguageSelector. The built-in version selector always read '26.10' due
  to our docs/<lang>/<version> layout (rspress expects the reverse), and
  the built-in language selector only showed a globe icon. Both are hidden
  via CSS and replaced.

- Config: search.versioned, enableScrollToTop and per-locale editLink to
  GitHub. Clear themeConfig.nav since the custom theme provides nav items.

- tsconfig: jsx + DOM lib so the IDE typechecks the new theme files.

- migrate.py: normalize (../)+assets/... references to the correct depth
  based on the file's location, and skip FR-only files with no EN
  counterpart (they pulled in stale broken asset paths).

- Remove 8 already-migrated FR orphan files the new filter would now skip.
- Add pp (Monitoring Connectors) and cloud migration to migrate.py: 745
  pp files and 119 cloud files per language, plus their CommonJS sidebar
  files loaded via node. Factor the per-tree migration loop into
  _migrate_tree() so versioned-docs, pp and cloud share the same code path
  (orphan FR filter, asset copy, asset-with-spaces rename, asset-ref depth
  normalization).

- generate_sidebar: include /pp/, /cloud/ and their /fr/ variants, and
  emit non-default keys (/25.10/, /pp/, /cloud/, /fr/25.10/, ...) before
  the default keys (/, /fr/). rspress sidebar matching is first-match so
  the default `/` key would otherwise swallow every nested request.

- Tighten the code-fence language regex so inline triple-backtick spans
  like ```rrdcached``` is not supported. don't get falsely treated as
  fence openers. Trailing fence attributes (e.g. ```bash {1,3}) are
  preserved by capturing them.

- Add shiki aliases for languages not bundled in rspress v2 beta:
  smarty/csv/txt → text, cmd → bash.

- theme: replace the external cloud link with a local /cloud/... link
  (now that the cloud docs are migrated locally).

- src/sidebar.ts and rspress/docs/{en,fr}/{pp,cloud} regenerated.
- Add LM_SOURCE_{EN,FR}, LM_SIDEBAR_JS, LM_FR_LABELS_PATH constants and a
  migrate_logmanagement() call backed by the existing _migrate_tree helper.

- generate_sidebar: register /logmanagement/ and /fr/logmanagement/ in the
  non-default key group so they sit before / and /fr/ (rspress's first-match
  sidebar resolution).

- theme: add a "Log Management" Link in the top nav, language-prefixed and
  pointing at the local /logmanagement/getting-started/welcome.

18 markdown files + 10 assets per language.
The rspress v2 stable line moved to a new npm package name: @rspress/core
(the rspress package itself stays at v1.47.x). The CLI binary is still named
rspress, only imports and the package name change.

Changes:

- package.json: replace `rspress@2.0.0-beta.21` with `@rspress/core@~2.0.14`,
  add `"type": "module"` (eliminates a parse-as-CJS warning + perf overhead
  rspress v2 prints on startup).

- pnpm-workspace.yaml: declare `rspress` as a workspace member so the lockfile
  tracks its deps properly. Add `minimumReleaseAgeExclude` for `@rspress/core`
  and `@rspress/shared` so the 7-day minimum-release-age rule doesn't block
  picking up fresh @rspress/core releases.

- rspress.config.ts: switch `defineConfig` import to `@rspress/core`. The
  `markdown.checkDeadLinks` option moved to `markdown.link.checkDeadLinks` in
  the v2 stable API. Add an opt-in Rsdoctor integration via
  `builderConfig.tools.rspack` — set `RSDOCTOR=true pnpm build` to instrument
  the build and open the analysis dashboard.

- theme/index.tsx: switch to named imports from `@rspress/core/theme-original`
  per the v2 breaking change notice (the previous `default` export from
  `rspress/theme` is gone; the migration is named exports from `theme-original`).

- migrate.py: rewrite injected `<Tabs>/<TabItem>` imports to point at
  `@rspress/core/theme` instead of `rspress/theme`. All ~2005 affected MDX
  files regenerated.

- Add `@rsdoctor/rspack-plugin` as devDependency (pinned to ^1.5.12 because
  1.5.13 is too recent under minimumReleaseAge).
Bring the Rspress documentation visually in line with the production
Docusaurus site (docs.centreon.com) for both style and navigation:

- Add the Centreon navbar logo (light/dark variants) and favicon.
- Set the brand palette via a global stylesheet: Centreon blue
  (#2213e2) in light mode, green (#37d6ae) and navy background
  (#00003d) in dark mode, overriding the default Rspress theme vars.
- Load the site typography: Red Hat Display for body text and
  Esphimere for headings.
- Tint blockquotes and inline code with the brand color, and add a
  footer with the corporate links and copyright.

Font faces are referenced through paths relative to the stylesheet so
Rspack resolves and emits them; root-absolute url() paths failed
module resolution and dropped the whole global stylesheet.

Assisted-by: Claude Code (claude-opus-4-8)
Continue the visual alignment with the production Docusaurus site
(docs.centreon.com):

- Highlight code with the github-light / dracula Shiki themes, matching
  the Prism themes used on the Docusaurus site.
- Enable click-to-zoom on documentation images via medium-zoom,
  re-attaching on each navigation and tinting the overlay per theme
  (white in light mode, Centreon navy in dark mode).
- Render a global footer on documentation pages (corporate links, logo
  and copyright) through the DocLayout afterDocFooter slot.
- Raise the navbar height to 96px to match the Docusaurus header.

The official @rspress/plugin-medium-zoom targets the rspress 1.x runtime
and is incompatible with 2.x, so the medium-zoom library is wired up
directly in the custom theme instead.

Assisted-by: Claude Code (claude-opus-4-8)
Two issues surfaced by visual review of the rendered site:

- Logos (navbar + footer) and homepage images were broken because the
  assets lived in rspress/public, but rspress serves the public folder
  located under the doc root (docs/public). Move images to
  docs/public/img and keep the fonts bundled by Rspack under
  styles/fonts (referenced relatively from the global stylesheet).
- The navbar showed duplicate language/version selectors: the rule that
  hid rspress's built-in switchers targeted the obsolete .translation
  class. In rspress 2.x NavLangs/NavVersions render as plain
  .rp-nav-menu__item elements inside .rp-nav__others, so hide those
  while keeping the appearance toggle and social links.

Assisted-by: Claude Code (claude-opus-4-8)
Clicking any sidebar entry on a 26.10 (default version) page led to a
404. The migration keyed the default version under '/' with
version-less links (/getting-started/foo), assuming rspress would serve
the default version at the root. It does not: pages are only served
under /<version>/..., and the custom version switcher
(VersionAwareNav) already builds /<version>/... links. Version-less
URLs are unrouted and 404.

Key the default version like the others (/26.10/ and /fr/26.10/) with
version-prefixed links, in both migrate.py and the generated
src/sidebar.ts. Non-default versions, pp, cloud and logmanagement are
unchanged.

Assisted-by: Claude Code (claude-opus-4-8)
On viewports below 1280px the custom version-aware nav rendered its
links inline, so they overflowed and overlapped the logo. Collapse it
into a hamburger button at the top right that opens a panel with the
product links, the version and language selectors, and the appearance
toggle. rspress's own mobile hamburger is hidden so only one burger is
shown. The desktop layout (>=1280px) is unchanged.

Assisted-by: Claude Code (claude-opus-4-8)
Add the Experience Monitoring (XM) product documentation, migrated from
Docusaurus like the other standalone trees (pp, cloud, logmanagement):
English and French content plus assets, served under
/experience-monitoring/ and /fr/experience-monitoring/ with their own
sidebar sections. migrate.py gains the sources, a migrate_experience()
step and the sidebar generation for it.

This is needed so the product switcher can link to Experience
Monitoring without a 404.

Assisted-by: Claude Code (claude-opus-4-8)
Add a grid (apps) icon at the top right that opens a product switcher,
mirroring docs.centreon.com: Infrastructure Monitoring (IM),
Experience Monitoring (XM) and Log Management (LM), each with its
coloured initials badge. Infrastructure Monitoring is version-aware.
Experience Monitoring is also added to the mobile burger panel so it
stays reachable on small screens.

Assisted-by: Claude Code (claude-opus-4-8)
Derive the current product family (Infrastructure Monitoring,
Experience Monitoring, Log Management) from the URL and show only that
family's sections in the navbar header: Infrastructure Monitoring keeps
OnPrem / Cloud / Connectors, while Experience Monitoring and Log
Management show only themselves.

Show the version selector only on actually-versioned pages (the on-prem
docs). It is now hidden on Experience Monitoring and Log Management — as
well as Cloud and Connectors — where versions don't exist and switching
would 404.

The product switcher highlights the active family and the mobile panel
mirrors the same product-aware logic.

Assisted-by: Claude Code (claude-opus-4-8)
Lay the navbar out like docs.centreon.com: the product sections move to
the left next to the logo (with the active one highlighted), and the
right cluster is ordered Search, version, language, appearance toggle,
product switcher. The version selector gains the star marker. rspress's
built-in 'others' group is fully hidden since the appearance toggle is
now rendered in our own cluster in the right position.

Assisted-by: Claude Code (claude-opus-4-8)
The homepage cards still linked to docs.centreon.com (and the Infra
OnPrem link was version-less, hitting a 404). Now that Experience
Monitoring, Cloud, Connectors and Log Management are all migrated,
repoint every card link to the local pages: Infra OnPrem to the
versioned /26.10 path, the others to their product trees. Docusaurus
/category/ links are mapped to real pages (digital sobriety, the
OpenTelemetry collector page). English and French homepages updated.

Assisted-by: Claude Code (claude-opus-4-8)
The footer was attached to the doc layout's afterDocFooter slot, so it
only showed on documentation pages and was missing from the custom
homepage. Move it to the Layout bottom slot so it renders full-width at
the bottom of every page (homepage included), matching the docs.centreon.com
footer.

Assisted-by: Claude Code (claude-opus-4-8)
The language selector always inserted a /<version>/ segment, so
switching language off the on-prem docs went to broken targets: from
the homepage it landed on /fr/26.10 instead of the French homepage /fr,
and on cloud/connectors/experience/log management pages it produced
/fr/26.10/<tree>/... (404). buildPathname now only adds the version on
versioned pages, so language switching preserves the current location
everywhere (homepage included).

Assisted-by: Claude Code (claude-opus-4-8)
Images are the largest part of the output (~96 MB) and are served as-is,
so gzip can't shrink them — unlike JS, reducing them cuts the deployed
bundle and per-page transfer directly. Add @rsbuild/plugin-image-compress
to the builder config to compress PNG/JPEG/ICO at build.

Result: PNG output 71.5 MB -> 33.4 MB (-53%), total emitted 154 MB -> 114 MB
gzipped (-40 MB), for +8s build and no visible quality loss. GIF/SVG are
left untouched.

Assisted-by: Claude Code (claude-opus-4-8)
Images were already compressed at build, but GIF and MP4 have no
build-time codec and were served raw at full size. Optimize them in the
migration with vendored binaries:
- GIFs via gifsicle (-O3 --lossy=80): output 24.1 MB -> 15.5 MB.
- The demo video via ffmpeg/libx264 (crf 30, 128k audio): 20.8 MB -> 3.5 MB.

Combined with the earlier image compression, the deployed bundle drops
from 154 MB to 87 MB gzipped (-43%). migrate.py gains an optimize_media()
step (no-op when the tools are absent) and ffmpeg-static/gifsicle are
added as dev tooling. No visible quality loss.

Assisted-by: Claude Code (claude-opus-4-8)
Build the rspress docs in CI and deploy a Cloudflare Pages preview on
every PR touching rspress/, posting the URL as a sticky comment. Each PR
gets its own stable preview URL; deploying prebuilt assets via wrangler
doesn't use the Cloudflare build quota, so it scales to many concurrent
PRs for free.

Actions are SHA-pinned (reusing the repo's existing pins); wrangler runs
via pnpm dlx so no extra action needs pinning. Previews are skipped for
fork PRs (no access to secrets).

Requires a maintainer to create the Cloudflare Pages project
'centreon-docs-preview' and set CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID.

Assisted-by: Claude Code (claude-opus-4-8)
…imit

The French search index (25.07 MiB) exceeds Cloudflare Pages' 25 MiB
per-file hard limit, which would fail the preview deployment. Drop code
blocks from the search index only on preview builds (PREVIEW_BUILD=true,
set by the preview workflow): the fr index drops to 22.8 MiB, comfortably
under the limit. Staging and production keep the full search index
(code blocks included) since they deploy to self-hosted infra with no
such limit.

Assisted-by: Claude Code (claude-opus-4-8)
…ning)

- Make the nav dropdowns keyboard- and screen-reader-accessible: real
  <button> trigger with aria-haspopup/aria-expanded, open on focus/click,
  close on Escape and focus-out (the mobile burger was already accessible).
- Give the homepage social links an accessible name (alt + aria-label).
- Anchor familyOf()/activeSection() on the path prefix (after lang/version)
  instead of substring matching, so doc slugs containing cloud/pp/... aren't
  misclassified.
- Pin wrangler via the lockfile (devDependency) and call it with pnpm exec
  instead of fetching the latest at runtime with pnpm dlx in a job holding
  the Cloudflare token.
- Log ffmpeg/gifsicle stderr on failure in migrate.py and drop an unused var.

Assisted-by: Claude Code (claude-opus-4-8)
@kduret kduret requested a review from a team as a code owner June 18, 2026 12:08
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor
PR Previews
🚀 Deployed preview to https://docs-preview-int.centreon.com/previews/pr-5415/staging/
🚀 Deployed preview to https://docs-preview-int.centreon.com/previews/pr-5415/next/
at Thu, 18 Jun 2026 13:18:29 GMT

NOTE: Previews are deleted after 30 days of inactivity

kduret added 3 commits June 18, 2026 14:49
Re-run the migration against the source docs brought in by the staging
resync, regenerating rspress/docs and the sidebar. Also harden migrate.py
so re-runs are reproducible:

- Clean each destination tree before writing, so a changed JSX detection
  (foo.md -> foo.mdx) no longer leaves a stale file and duplicate route.
- Normalize bare/relative image asset references (assets/... and ./assets/...)
  to the correct depth-aware path, so same-directory images don't compile to
  unresolvable MDX bare imports.

Build verified (preview build passes; search indexes stay under 25 MiB).

Assisted-by: Claude Code (claude-opus-4-8)
Truncate each page's content in the generated search index to 12000 chars
on preview builds, dropping the per-language index from ~24 MiB to ~14 MiB.
This keeps comfortable headroom under Cloudflare Pages' 25 MiB per-file
limit as the docs grow, while preserving useful search on previews.
Preview-only (post-build step in the preview workflow); staging and
production keep the full search index.

Assisted-by: Claude Code (claude-opus-4-8)
Build the PR-preview search index from page headings (title + H2/H3)
rather than truncating page bodies. Reviewers can search by title and
section, and the per-language index drops to ~2.9 MiB (from ~24 MiB) for
ample headroom under Cloudflare Pages' 25 MiB per-file limit. Staging and
production keep the full-text search index.

Assisted-by: Claude Code (claude-opus-4-8)
@kduret kduret marked this pull request as draft June 18, 2026 13:06
- Cache rspack's persistent build cache (node_modules/.cache) in the
  preview workflow so successive builds reuse it (rspack recompiles only
  changed modules).
- Skip the Docusaurus build/preview jobs for the rspress migration PR by
  guarding get-versions on the branch (the rspress site has its own
  preview). Other branches and the staging deploy are unaffected; remove
  the guard once the migration branch merges.

Assisted-by: Claude Code (claude-opus-4-8)
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

📘 rspress documentation preview

https://pr-5415.centreon-docs-preview.pages.dev

Updated on every push to this pull request.

kduret added 2 commits June 22, 2026 10:27
Low-risk build optimizations identified while reviewing the rspress
config options:
- output.legalComments: 'none' — drop the separate *.LICENSE.txt files.
- performance.printFileSize.compressed: false — skip the per-asset gzip
  size computation (costly across thousands of files, no functional impact).
- markdown.cjkFriendlyEmphasis: false — the docs are EN/FR only (no CJK).
- markdown.image.checkDeadImages: false on preview builds only (kept on for
  staging/production to catch broken image references before deploy).

Assisted-by: Claude Code (claude-opus-4-8)
Set output.minify.jsOptions.minimizerOptions.compress.passes to 1 (rspack
defaults to 2 since web-infra-dev/rspack#8853). Minification is the dominant
build cost; one pass recovers a chunk of that time with no measurable size
change (JS stays 176 MB raw / 21 MB gzipped — the gzipped transfer is
already near-optimal).

Assisted-by: Claude Code (claude-opus-4-8)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant