Documentation site for OVHcloud built with Rspress v2. Serves 7 locales (fr, en, de, es, it, pl, pt) with 9500+ MDX pages.
Coming from ovh/docs? See CONTRIBUTING.md for what changed (folder layout, frontmatter, MDX, images, links).
- Node.js 24+
- pnpm
pnpm install
pnpm dev # Start dev server
pnpm build # Production build (all locales)
pnpm preview # Preview production buildBy default, pnpm dev serves fr and en locales only (for performance).
Use the DEV_LOCALES environment variable to control which locales are active:
DEV_LOCALES=fr pnpm dev # French only (fastest)
DEV_LOCALES=fr,en,de pnpm dev # French, English, German
DEV_LOCALES=fr,en pnpm dev # French + English (default)Only active locales have their content compiled, sidebar generated, and routes registered. This significantly reduces SSR time on the large MDX codebase.
With 9500+ MDX files, dev SSR is ~9s per page (Rspress MDX compilation overhead). Optimizations applied:
lastUpdateddisabled in dev (runs git log per page), enabled in production only- Shiki
markdownandmdxlangs removed (they disable lazy loading) - Reducing DEV_LOCALES to a single locale helps with initial startup
rspress.config.ts— Development, serves all active locales from one instancerspress.config.build.ts— Production, builds one locale at a time viaLOCALEenv var
pnpm build # Parallel builds, concurrency 2 (default)
pnpm build:fast # Parallel builds, concurrency 4
pnpm build:low-mem # Sequential builds, concurrency 1build:cachegenerates the lastUpdated cache (git log dates)- Turborepo runs
build:{locale}tasks in parallel - Each locale build outputs to
dist/{locale}/ build:combinemerges all locale builds into finaldist/
LOCALE=fr rspress build -c rspress.config.build.tsdocs/
{locale}/guides/ Content per locale (fr, en, de, es, it, pl, pt)
public/ Shared static assets
config/
sidebar/ Sidebar definitions per product category
nav/ Navigation with localized external URLs
shared.ts Locale definitions, shared Rspress config
theme/
index.tsx Theme entry point (extends @rspress/core/theme-original)
components/ UI components: Nav, Sidebar, Breadcrumbs, OverviewCTA...
layouts/ Custom layouts: OverviewLayout, ELearningLayout, MigrationLayout
components/ Reusable MDX components: Carousel, LinkCard, Api...
scripts/ Build & utility scripts
i18n.json UI translation keys
Configured via frontmatter pageType:
| Type | Description | Frontmatter |
|---|---|---|
doc |
Standard documentation page (default) | — |
overview |
Product overview with essentials, getting started, tutorials, go further, CTA | pageType: overview |
e-learning |
Training page with hero, courses list, CTA | pageType: e-learning |
migration |
Migration guide with sections, resources, CTA | pageType: migration |
home |
Landing page | pageType: home |
The sidebar is generated from a single markdown file and supports full i18n across all 7 locales.
| File | Role |
|---|---|
config/sidebar/index.md |
Source of truth — markdown tree defining the full sidebar structure |
config/sidebar/parser.ts |
Parses index.md into Rspress SidebarGroup[] with i18n keys |
config/sidebar/index.ts |
Entry point — creates the sidebar per locale, handles dev/prod routing |
config/sidebar/supplements.ts |
Header items (API ref, changelog…) and Security section (not in index.md) |
i18n.json |
Contains sidebar.gen.* translations for non-leaf labels |
base/pages/index-translations.{locale}.yaml |
Source YAML translations for products/sections |
+ Universe Name ← top-level group (indent 0, no link)
+ [Product Label](products/category-ref) ← product group (link starts with products/)
+ [Section Label](section-ref) ← section group (link without /)
+ [Guide Title](path/to/guide-slug) ← leaf guide (link with /, Rspress path format)Classification rules:
- indent 0, no link → universe (e.g.
+ Public Cloud) - link starts with
products/→ product group (collapsible) - link without
/→ section group (collapsible) - link with
/→ guide leaf (converted to/guides/...link)
- Non-leaf nodes (universes, products, sections) use i18n keys (
sidebar.gen.*), resolved at render time fromi18n.json - Leaf nodes (guides) read titles directly from the MDX frontmatter of the target locale at build time
Universe names use hardcoded translations in parser.ts (UNIVERSE_TRANSLATIONS). Product/section labels come from base/pages/index-translations.{locale}.yaml.
Add/remove/reorder a guide:
- Edit
config/sidebar/index.md— add or remove the+ [Guide Title](path/to/slug)line - Run
pnpm sidebar:validateto check all links resolve to existing.mdxfiles
Add/remove a product or section:
- Edit
config/sidebar/index.md - Run
pnpm sidebar:sync-i18nto generate/update i18n keys ini18n.json
Add a new universe:
- Add the
+ Universe Nameblock inconfig/sidebar/index.md - Add translations in
UNIVERSE_TRANSLATIONSinconfig/sidebar/parser.ts - Run
pnpm sidebar:sync-i18n
pnpm sidebar:validate # Check sidebar links → existing .mdx files
pnpm sidebar:orphans # Find guides not in the sidebar
pnpm sidebar:check # Both above
pnpm sidebar:sync-i18n # Sync i18n keys from index.md → i18n.jsonRspress strips the URL prefix for the default locale (lang: 'fr'):
| Mode | FR (default locale) | Other locales |
|---|---|---|
| Dev | /guides/... |
/en/guides/..., /de/guides/... |
| Prod | /fr/guides/... |
/en/guides/..., /de/guides/... |
This affects sidebar key matching — see config/sidebar/index.ts.
Documentation pages use /links/ references for external URLs that vary by locale (OVHcloud Manager, API console, product pages, etc.). These are resolved at build time to concrete URLs via Rspress's native replaceRules.
In MDX content, authors write locale-agnostic links:
Log in to your [OVHcloud Control Panel](/links/manager).
Visit the [API console](/links/api).
Order a [VPS](/links/bare-metal/vps).At build time, Rspress replaces (/links/manager) with the actual URL for the target locale — e.g. (https://www.ovh.com/auth/...&ovhSubsidiary=fr) for fr.
config/links.ts ← Single source of truth: 219 link keys × 7 locales
config/link-rules.ts ← Generates ReplaceRule[] from links.ts for a given locale
rspress.config.ts ← Dev: replaceRules for the first active locale
rspress.config.build.ts ← Prod: replaceRules for the LOCALE being built
Replacement happens before MDX compilation (native String.replace()), so it works with mdxRS and has no performance impact.
Edit config/links.ts directly. Each entry maps a key to its URL per locale:
'bare-metal/vps': {
'fr': 'https://www.ovhcloud.com/fr/vps/',
'en': 'https://www.ovhcloud.com/en-gb/vps/',
'de': 'https://www.ovhcloud.com/de/vps/',
// ...
},Then use (/links/bare-metal/vps) in any MDX file — it resolves automatically.
If a locale-specific URL is missing, the system falls back: locale → en → first available.
The file config/links.ts was originally generated from base/links/ (the legacy link definitions). To regenerate:
npx tsx scripts/convert-links.tsThe legacy documentation system used /pages/ and /products/ paths for internal cross-references. These have been permanently converted to standard /guides/ paths.
| Legacy pattern | New pattern | Example |
|---|---|---|
(/pages/public_cloud/compute/foo_bar) |
(/guides/public-cloud/compute/foo-bar) |
Underscores → hyphens, /pages/ → /guides/ |
(/pages/bare_metal_cloud/dedicated_servers/ssh_intro#anchor) |
(/guides/bare-metal-cloud/dedicated-servers/ssh-intro#anchor) |
Anchors preserved |
(/products/public-cloud-compute) |
(/guides/public-cloud/compute) |
Flat slug → nested path |
New content should always use standard relative or absolute /guides/ paths — never /pages/ or /products/.
pnpm lint # Lint (Biome)
pnpm format # Format (Biome)
pnpm check # Lint + format with auto-fix
pnpm sidebar:check # Validate sidebar (structure + orphan detection)
pnpm page:create # Scaffold a new page
pnpm page:delete # Delete a page across all locales- Biome for linting & formatting (single quotes, space indentation)
- SCSS with Tailwind CSS v4 directives (
tw-darkclass for dark mode) - TypeScript with strict mode
- Path alias:
@components→components/