Skip to content

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

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

kduret wants to merge 60 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 15 commits June 22, 2026 13:49
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)
Extract react and node_modules into dedicated lib-react/lib-vendor
chunks so the browser keeps them cached across deployments. index.js
changes on every docs update; vendors rarely do. Merges with rspress's
existing styles cacheGroup (deep-merged, preserved).
Also bump @rsdoctor/rspack-plugin, wrangler, and align @types/node with Node 24.
Rspress 2.0.x warns about i18n keys missing for fr and falls back to English (search, outline, prev/next...).
rspress/docs is generated by migrate.py, so its git history only holds migration
commits; resolve each page to its source file like Docusaurus' showLastUpdateTime.
The preview checkout fetches the full (blobless) history; also move it to Node 24.
Pages are now pre-rendered (relative image paths resolve fine in 2.0.23) and
served without the .html extension, like docs.centreon.com. SSG runs one worker
per CPU, so the preview build is pinned to 2 CPUs to stay within the runner's
16 GB (15.3 GB peak measured locally, 8 min 16 s).
…cusaurus

migrate.py now post-processes the migrated pages:
- /docs/... (latest core version on docs.centreon.com) and /<version>/...
  absolute links become file-relative, as rspress strips the version segment;
- relative links Docusaurus resolves from the plugin content root;
- URL-relative links written for trailingSlash (../foo/) resolve from the file;
- linked downloadable files (.sql, .pdf, .cnf) are published to docs/public/.

Those 8,000+ links were rendered as-is and returned 404 on the rspress site.
Like Docusaurus' onBrokenLinks: throw. Links already broken in the Docusaurus
sources are listed explicitly. Anchors are not checked: rspress does not see
headings nested in JSX (Tabs) and would report false positives.
SSG got the 16 GB GitHub-hosted runner killed mid-build; it stays enabled for
other builds (15 GB peak measured with 2 CPUs). Retry wrangler deploys on
Cloudflare API rate limiting (code 10429), which failed the last two runs.
migrate.py converts the migrated PNGs with the same palette quantization as
the build's PNG compression, then encodes them as lossless WebP and updates
the page references. Images served drop from 33.3 MB (quantized PNG) to
20.3 MB; 18 PNGs that cannot be encoded or grow stay as-is.
The default theme renders every sidebar entry and hides collapsed groups
with CSS, so each SSG page carried the whole section sidebar (132 KB on a
26.10 page). The ejected Sidebar renders a group's children once it has been
expanded, like Docusaurus: HTML output drops from 829 MB to 372 MB (70 -> 45 MB
gzipped). Only SidebarGroup.tsx differs from the rspress 2.0.23 eject; the CSS
files are rspress' compiled ones.
26.10 and 25.10 are plain sections sharing one search index, so every query
returned both versions. Keep the current version's results (the latest one
outside versioned docs), like docs.centreon.com.
Like Docusaurus for Markdown images. Without loading="lazy", React preloaded
every image of the page in the SSG <head>, competing with critical resources.
The rspress migration copies documentation pages whose example values (API
responses, public key thumbprints, placeholders) predate the secrets scan.

Refs: MON-212387
kduret added 14 commits October 16, 2026 13:42
- Drop the page fields only the unused Overview component reads (toc, title,
  frontmatter) from the page list in the main bundle, and give each locale
  only its own sidebar sections (rspress duplicated the whole sidebar into
  every locale): index.js 6.6 MB -> 2.1 MB, 821 -> ~280 KB gzipped.
- Serve Esphimere as WOFF2 (74 -> 25 KB per weight) and self-host Red Hat
  Display with @fontsource instead of a render-blocking Google Fonts import.
- Cloudflare Pages _headers: hashed /static/ assets are immutable.
…zoom

- Footer: full-width band with the links on one side and the logo and
  copyright on the other, Esphimere bold links, inverted colors on doc pages
  and side by side on mobile, like the Docusaurus footer.
- The custom medium-zoom hook and rspress' built-in plugin both bound the doc
  images (.rp-doc.rspress-doc), opening two overlays: keep the built-in one,
  whose --rp-c-bg overlay follows the theme (white / Centreon navy).
- Move the nav and footer styles from inline <style> blocks, repeated in
  every SSG page, to theme/index.css.
The MBI sample report PDFs, SQL and cnf files were copied for every version
and language linking them (44 copies, 36 MB of PDFs). migrate.py now
publishes identical contents once under /downloads/ and points the links
there: 6 files, -18 MB.
The default nav rendered its own switch in the hidden .rp-nav__others and the
custom mobile panel was always rendered while hidden: three theme switches
(1.1 KB of SVG each) per page. Render nothing in the default nav and the
mobile panel only while open.
Each of the ~31,000 code blocks repeated four inline SVGs (copy, copied,
wrap, wrapped) in the SSG HTML. The theme overrides these icons with empty
spans drawn by a CSS mask defined once, with the same paths.
Docusaurus resolves [](/path/page.md) from the docs plugin content root; the
experience-monitoring release notes use it.
Re-run migrate.py on the current Docusaurus sources: descriptions added to
the frontmatter, ITSM pages reorganized into ticketing, new Map, CMA and RUM
pages, removed pages.
Use the site fonts instead of a system font stack and update the cards from
src/pages/index.js (no more Log Management BETA, new STM and RUM entries).
Like docs.centreon.com: breadcrumbs (home, product section, sidebar groups,
current page) and a version badge above the title, sidebar groups leading to
the current page colored, Esphimere bold navbar items and no section links on
the home page.
Values measured on docs.centreon.com: dark pill search box, 18px sidebar with
the active item colored instead of highlighted, 18px dark content text, 48px
bold h1, no rule above the first h2, bordered tables, h3-deep 14px outline,
smaller headings under 576px. Selectors are prefixed with html because the
global styles load before rspress' component styles.

This branch has not been deployed

No deployments
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