This repo replaces the legacy ovh/docs (Pelican + Hugo-flavored Markdown) with Rspress v2 + MDX. The authoring conventions are different. This guide is the shortest path from "I know ovh/docs" to "I can ship a guide here".
For build & dev-server setup, see README.md. For repo architecture, see CLAUDE.md.
| Topic | Legacy ovh/docs |
New guides-new-xp |
|---|---|---|
| File extension | .md |
.mdx |
| Folder layout | pages/{universe}/{product}/{slug}/guide.{lang}.md (one folder, all locales together) |
docs/{locale}/guides/{universe}/{product}/{slug}.mdx (one tree per locale) |
| Locale codes | fr-fr, en-gb, de-de, … |
fr, en, de, es, it, pl, pt |
| Images | Co-located images/ next to the guide |
Centralized in docs/public/images/, referenced as /images/... |
| Frontmatter | title, slug, excerpt, section, order, updated, … |
title, description, lastUpdated |
| Sidebar / order | Per-section _meta.json / Pelican config |
Single source config/sidebar/index.md |
| Internal links | /pages/..., /products/... |
/guides/... |
| Locale-aware external links | Hand-written per locale | /links/<key> resolved at build from config/links.ts |
| Admonitions | > [!warning] blockquotes |
:::warning ... ::: (blockquote style still works) |
| Tabs / FAQ / API blocks | Hugo shortcodes | MDX components, imported at top of file |
The big shift: instead of all language variants of a guide living in one folder, each locale now has its own tree. The same guide lives at the same path under each locale.
Legacy:
pages/public_cloud/compute/getting_started/
guide.en-gb.md
guide.fr-fr.md
images/
New:
- docs/en/guides/public-cloud/compute/getting-started.mdx
- docs/fr/guides/public-cloud/compute/getting-started.mdx
Locales under docs/: de, en, es, fr, it, pl, pt.
Universes under each docs/{locale}/guides/:
account-and-service-managementbare-metal-cloudhosted-private-cloudmanage-and-operatenetworkovhcloud-labspublic-cloudstorage-and-backupweb-cloud
Slugs use hyphens, not underscores — both in folder names and in filenames. The legacy public_cloud/getting_started becomes public-cloud/getting-started.
In ovh/docs, a guide's slug came from the meta.yaml full_slug field — typically a long, descriptive string like public-cloud-compute-getting-started. In this repo, the slug is just the filename, and the convention is to strip the path prefix to keep slugs short.
The rule (codified in scripts/generate-slug-mapping.ts) is:
- Take the legacy
full_slug. - Strip a leading
{universe}-{product}-prefix if present. - Otherwise strip a leading
{product}-prefix if present. - Otherwise keep the slug as-is.
Legacy full_slug |
New file path |
|---|---|
public-cloud-compute-getting-started |
docs/{locale}/guides/public-cloud/compute/getting-started.mdx |
dedicated-servers-ssh-intro |
docs/{locale}/guides/bare-metal-cloud/dedicated-servers/ssh-intro.mdx |
whois-ip (already short) |
docs/{locale}/guides/network/network-tools/whois-ip.mdx |
Practical rules of thumb when picking a name for a brand-new guide:
- Use kebab-case, lowercase only.
- Don't repeat the universe or product name in the slug — the path already carries it.
- Keep it descriptive but short (3–5 words). The slug becomes the URL.
- Make sure the slug is unique within its product folder.
pnpm page:createThis is an interactive prompt that asks for:
- The page path (must start with
guides/) - The page type (
docoroverview) - Visibility (public, or hidden via
_prefix) - Locales to generate
- Sidebar section to attach the new entry to
It creates the .mdx files for every selected locale, wires the entry into config/sidebar/index.md, and adds the i18n keys. To remove a guide across all locales, use pnpm page:delete.
:::danger Avoid renaming slugs. A published slug is a public URL. Renaming it breaks every external link, bookmark, and search engine result pointing at the guide, and requires redirections to be added (in CDN config, in config/links.ts, and potentially in legacy
ovh/docsredirects). Treat a rename as a last resort — only when the existing slug is wrong (typo, misleading, or actively harmful for SEO). For everything else (clarity, style, personal preference), leave the slug alone.
If you have confirmed with the docs team that a rename is genuinely necessary:
git mvthe.mdxfile under each of the 7 locales.- Update its entry in config/sidebar/index.md.
- Search-and-replace every occurrence of
/guides/.../old-slugin the repo. - Add a redirect from the old path to the new one so external links don't 404.
- Run
pnpm sidebar:checkto confirm nothing is dangling.
There is no one-shot CLI for arbitrary single renames; scripts/rename-slugs.ts only performs bulk renames driven by an SEO report.
- Stored once in docs/public/images/ — shared across all locales, never duplicated.
- Path convention:
/images/{universe}/{product}/{guide-slug}/{filename}.png - Shared assets (Control Panel screenshots, icons, etc.):
/images/assets/...
Reference from MDX with the absolute web path (leading /):
For thumbnails / clickable previews, use the JSX form so the thumbnail class applies:
<img className="thumbnail" alt="Instance creation" src="/images/guides/public-cloud/compute/.../24-instance-creation01.png" loading="lazy" />Do not use ../images/... — the migration rewrote every legacy relative path to /images/.... Sticking to the absolute form keeps things consistent and survives file moves.
:::tip A working example of every format below — admonitions, callouts, collapsible sections, tabs, action buttons, images, video embeds, code blocks, the full component set, etc. — lives in docs/en/internal/format-reference.mdx. When in doubt, copy from there. The page is rendered in dev and prod (so you can verify formats render correctly in both), but excluded from the sidebar, sitemap,
llms.txt, and indexed vianoindex,nofollow. :::
# Legacy (ovh/docs)
---
title: 'Getting started with Public Cloud'
slug: getting-started
excerpt: 'Find out how to...'
section: 'Compute'
order: 1
updated: 2024-09-01
---# New (guides-new-xp)
---
title: Getting started with Public Cloud
description: Find out how to...
lastUpdated: 2025-09-09
---slug, section, and order are gone. Position in the sidebar is now controlled by config/sidebar/index.md. description replaces excerpt. lastUpdated replaces updated (camelCase).
RSPress fences are preferred:
:::warning
This action is irreversible.
:::
:::info
Useful tip.
:::
:::details Expandable section title
Hidden content
:::The legacy GitHub-style blockquote syntax (> [!warning]) still renders, so existing copy-pastes keep working.
All seven callout types are supported. Each appears with its own icon and color in the rendered output:
:::info
Neutral information.
:::
:::tip
A best-practice or shortcut.
:::
:::note
Side notes that don't fit elsewhere.
:::
:::warning
Something that requires attention.
:::
:::caution
Stronger than warning, weaker than danger.
:::
:::danger
Irreversible or destructive actions.
:::When a callout ends with a list, leave a blank line before the closing :::. (Without it the fence is absorbed into the list and the callout never closes.)
:::tip
- First item
- Second item
:::Use :::details (replaces the legacy <FAQ> / <FAQItem> components):
:::details Can I resume an installation if it fails?
No, you'll need to start over.
:::The header after :::details is the visible toggle. Multiple :::details blocks in a row render as a FAQ-style list.
UI button names use <code className="action"> — JSX, not the legacy {.action} Markdown attribute:
Click <code className="action">Create instance</code>.
Open <code className="action">Tools & Settings</code>, then <code className="action">License information</code>.The migration script rewrote every legacy
`Button`{.action}to this JSX form. The Markdown attribute syntax does not work in MDX.
Tabs require an import at the top of the file:
import { Tab, Tabs } from '@rspress/core/theme';
<Tabs>
<Tab label="Linux">Steps for Linux…</Tab>
<Tab label="Windows">Steps for Windows…</Tab>
</Tabs>Tab selection syncs by default. Tab blocks that share the same labels sync their selected tab and remember it across pages — so all "Via the OVHcloud Control Panel" / "Via the OVHcloud API" blocks (or Linux / Windows, etc.) switch together. This is automatic; you don't add anything.
Sequential "Step" tabs are never synced. Numbered sets — Step 1 / Step 2 / Step 3 (including branched ones like Step 3 - IMAP / Step 3 - POP3), Option 1 / Option 2, Étape 1… — are detected automatically and kept independent, so picking a step in one block won't move another block (on the page or across navigation). No action needed.
Opting a block out of sync (noSync) — for the rare non-sequential block that still shouldn't sync, add the noSync prop:
<Tabs noSync>
<Tab label="Plan A">…</Tab>
<Tab label="Plan B">…</Tab>
</Tabs>(Detection and the noSync opt-out live in theme/components/SyncedTabs/index.tsx.)
Inline images use the standard Markdown syntax with an absolute /images/... path:
For thumbnail-style images (clickable preview, smaller rendered size), switch to the JSX form so the thumbnail class applies:
<img className="thumbnail" alt="Instance creation" src="/images/.../instance-01.png" loading="lazy" />YouTube videos are embedded with a plain <iframe> — note the class="video" (not className, this is one of the few HTML-style attributes accepted by the MDX pipeline here):
<iframe class="video" width="560" height="315"
src="https://www.youtube-nocookie.com/embed/<id>"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>Use raw HTML when you specifically need target="_blank":
<a href="https://www.ovhcloud.com/en-gb/domains/" target="_blank">domain name</a>For the common case (regular external links), prefer /links/<key> (see Links below) so the URL is locale-aware.
Standard GitHub-flavored Markdown tables work as-is:
| Column A | Column B |
|---|---|
| value | value |Cells wrap automatically and the theme makes tables responsive, so a few guidelines keep them readable on every screen:
Width — prefer ≤4 columns; they fit any screen. 6+ columns scroll horizontally on phones (acceptable for reference tables, but check each column earns its place). For a genuinely wide table (8+ columns), transpose it (if it has few rows and many columns) or split it into smaller grouped tables. There is no mobile "card" layout — wide tables scroll horizontally by design (a matrix can't be stacked without losing the grid).
Cell content
- Write naturally — cells wrap on their own. Don't add
<br/>just to force wrapping; use it only for genuinely separate lines (e.g. several IP addresses in one cell). - Long URLs and paths can't word-break and will widen the table. Use link text —
[label](url)— or a/links/<key>(see Links) instead of pasting a bare long URL. - Never write a bare angle-bracket placeholder like
<SID>or<region>in a cell — MDX parses it as an unknown HTML tag and drops it. Backtick it (`<SID>`) or escape it (<SID>).
Format — always include the header row; align columns with :--- (left), :---: (center), ---: (right). Prefer Markdown tables; reserve a raw HTML <table> for merged cells (rowspan/colspan) that Markdown can't express.
Same triple-backtick syntax as before, with language tags. Common languages used across the repo: bash, console, json, yaml, ini, sql, python, go, javascript, typescript, dockerfile, nginx, apache, terraform. Avoid markdown and mdx — they disable Shiki's lazy loading and slow the dev server.
Use the <Api> component for OVHcloud API endpoint references, with its import:
import Api from '@components/Api';
<Api version="v1" section="/dedicatedCloud" method="POST" route={"/dedicatedCloud/\{serviceName\}/sap"} />Escape path parameters as \{serviceName\} inside the route={"…"} expression. The pill renders block-like: introduce it with a full sentence ending in a colon ("… using the following API call:") — never place it mid-sentence. For an endpoint link inside running text or a table cell, use <ApiLink section route method> instead — that one is auto-registered, no import (see Zone-aware API links).
To target Kimsufi / So you Start specific endpoints (not the OVHcloud API), add brands — a list of brands, same shape as regions (default ['ovh']) — and drop version, which the ks/sys brands don't have (no versioned API like OVHcloud's):
<!-- All 4: So you Start EU/CA + Kimsufi EU/CA -->
<Api brands={['sys', 'ks']} section="/dedicated/server" method="GET" route={"/dedicated/server/\{serviceName\}/install/compatibleTemplates"} />Some API calls are available on So you Start endpoints only, not on Kimsufi endpoints:
<!-- So you Start only (EU/CA) -->
<Api brands={['sys']} section="/dedicated/server" method="GET" route={"/dedicated/server/\{serviceName\}/install/compatibleTemplates"} />'ovh', 'ks' and 'sys' are independently selectable — mix them freely (e.g. brands={['ovh', 'sys']} for OVHcloud + So you Start only, or brands={['ks']} for Kimsufi only). Defaults to brands={['ovh']} (the standard EU/CA OVHcloud console, whose version defaults to 'v1' if omitted), offering both EU and CA unless restricted with regions — e.g. regions={["eu"]} on a brands={['sys']} call shows only So you Start EU. Note regions is validated against a Kimsufi/So you Start-specific EU/CA set when brands includes ks/sys, kept separate from the OVHcloud regions so the two can diverge (e.g. if OVH later adds a third zone that Kimsufi/So you Start never will).
Import from @components/... at the top of the MDX file:
import { LinkCard } from '@components/LinkCard';
import { Carousel } from '@components/Carousel';
import { GuidedTour } from '@components/GuidedTour';
import Tooltip from '@components/Tooltip';| Component | Purpose |
|---|---|
LinkCard |
Card link with title + description; auto-detects internal vs external |
Carousel |
Scrollable carousel of items (title, details, link, icon) |
GuidedTour |
Interactive guided tour (screenshots + spotlight annotations) |
Tooltip |
Hover tooltip. Glossary-backed: <Tooltip term="vrack">vRack</Tooltip> (canonical key from config/glossary/en.yaml; aliases are not lookup keys). One-off note: <Tooltip content="**markdown** allowed">word</Tooltip>. Do not hand-tag glossary terms — tagging happens during the proofread pass. |
AIChatbot |
Federated AI chatbot module (rare; usually only on landing pages) |
See components/ for the full list of props.
Because files are MDX, raw < and { in prose can break parsing. Either wrap them in backticks or escape them: \{serviceName\}, `<value>`. If a guide fails to compile with a cryptic error pointing at a paragraph of plain prose, this is almost always the cause.
Always use /guides/{universe}/{product}/{slug} (hyphens, no underscores). Anchors are preserved:
See the [Public Cloud getting started guide](/guides/public-cloud/compute/compute-getting-started).
Jump to the [SSH section](/guides/bare-metal-cloud/dedicated-servers/ssh-intro#generating-keys).Legacy /pages/... and /products/... paths have been permanently rewritten to /guides/... — do not introduce new ones.
For URLs that change per locale (Manager, product pages, order pages, etc.), use /links/<key>. The key resolves at build time to the right URL for the locale being built. Keys are defined in config/links.ts.
Typical example — product / order pages on ovhcloud.com:
Order a [Public Cloud Compute instance](/links/public-cloud/compute).
Browse the [Bare Metal range](/links/bare-metal/bare-metal).Control Panel links are not /links/ keys — use the zone-aware <ManagerLink to="/#/…">your service</ManagerLink> component instead (it follows the reader's zone and wraps the auth flow; hardcoded manager.*.ovhcloud.com URLs fail the build). Same logic for API links — see the next section.
The fallback chain is target locale → en → first available, so a missing translation never breaks the link.
To add a new key, edit config/links.ts and add a row with one URL per locale. Then use (/links/your-new-key) in any MDX file.
Links to the OVHcloud API depend on the reader's commercial zone (EU / CA), not the page locale — a locale-keyed /links/ entry cannot express that, and a hardcoded https://eu.api.ovh.com/… URL sends CA/APAC readers to an auth they cannot log in to. Use the zone-aware components instead (they follow the same zone selection as <ManagerLink>). Both are globally registered — do not add an import:
<!-- Generic reference to the API / the API console -->
You can also do this with <ApiLink>the OVHcloud API</ApiLink>.
<!-- Console deep link: section, with custom link text -->
All API routes used in this guide are in the <ApiLink section="/dedicated/nasha">*/dedicated/nasha* section of the API console</ApiLink>.
<!-- Console deep link: operation, as a normal inline link (tables, prose) -->
Retrieve them with the <ApiLink section="/me" method="GET" route={"/me/logs/audit"}>audit log API call</ApiLink>.
<!-- Token creation with pre-filled rights -->
<CreateToken rights="GET=/*&POST=/*&PUT=/*&DELETE=/*">Generate OVHcloud API tokens</CreateToken>Without props, <ApiLink> targets the API gateway page (https://api.{eu|ca}.ovhcloud.com/), which links onward to the console — use that for any generic mention. Console links are only ever section/operation deep links via the props above; never hardcode a console URL. Do not use /links/api or /links/console — they are zone-blind and deprecated. This is enforced at build time: a hardcoded API root/console/createToken URL (or one of the deprecated keys) fails the build with a pointer to this section.
<ApiLink> also supports the same brands/regions props as <Api> (see API endpoint blocks above), for an inline link to a Kimsufi / So you Start endpoint instead of the OVHcloud API:
<!-- So you Start + Kimsufi, both regions — picker offers all 4 -->
Consult it in <ApiLink brands={['sys', 'ks']} method="GET" route={"/dedicated/server/\{serviceName\}"}>the Kimsufi/So you Start console</ApiLink>.
<!-- So you Start only, EU only — no picker, direct link -->
See <ApiLink brands={['sys']} regions={["eu"]} method="GET" route={"/dedicated/server/\{serviceName\}"}>this endpoint</ApiLink>.Same rule as <Api>: drop version when brands doesn't include ovh (it has no effect and triggers a dev-only console warning), and regions is validated against the Kimsufi/So you Start EU/CA set, not the OVHcloud one.
EU and CA API schemas differ per route. Before deep-linking an operation (with <Api> or <ApiLink>), check it exists in both https://api.eu.ovhcloud.com/1.0/<section>.json and https://api.ca.ovhcloud.com/1.0/<section>.json — a console deep link to an operation the zone doesn't have falls back to the section root. If only one zone has it, add regions={["eu"]} (renders a plain single-zone link). EU-only products (SMS, Email Pro, …) are restricted automatically — no prop needed.
Deliberate exceptions — keep these as plain URLs, do not convert to components:
- an API endpoint as a value to copy into code (e.g. the OAuth2 token endpoints in a
curlexample) — explicit EU/CA URLs in backticks; a zone-aware component would hide the variant the reader needs to copy (code blocks are never touched anyway) - console UI-anchor links that don't target an operation (
#auth,#servers,?branch=v2without a section)
Pick the component by what you are pointing at:
| You want to reference… | Use | Not |
|---|---|---|
| The OVHcloud API / the console in general | <ApiLink> |
hardcoded URLs, /links/api|console |
| A specific endpoint the reader should call, as its own display element | <Api version="v1" section="…" method="GET" route={"…"} /> after a full sentence ending in a colon |
a mid-sentence pill; prose like "open the console and navigate to the /dedicated/server section in the left-hand menu" |
| A section or operation as an inline text link (running prose, table cells) | <ApiLink section="…" [method="GET" route={"…"}]>your link text</ApiLink> |
hardcoded ?section= console URLs |
| Either of the above, but on a So you Start / Kimsufi endpoint | add brands={['sys', 'ks']} (or ['sys']/['ks']), drop version |
hardcoded kimsufi.com/soyoustart.com console URLs |
| Token creation with rights | <CreateToken rights="…"> |
hardcoded createToken URLs |
Also skip "log in to the API console first" steps — the gateway and console are public pages, and the console offers authentication itself, both globally (the Authentication sidebar entry) and contextually on each operation. The sign-in mechanics are covered once in First steps with the OVHcloud APIs; do not repeat them per guide. See the format reference §10/§10b for working examples of all three components.
For external URLs that are the same for everyone (GitHub, third-party docs), use a regular Markdown link:
[Plesk documentation](https://docs.plesk.com/)The sidebar is a single tree in config/sidebar/index.md. After editing it:
pnpm sidebar:sync-i18n # if you added or renamed a non-leaf node
pnpm sidebar:validate # check every link points to an existing .mdx
pnpm sidebar:orphans # find guides not yet wired up
pnpm sidebar:check # validate + orphans in one goGuide titles shown in the sidebar are read from each MDX file's frontmatter, not from the sidebar file. Universe / product / section labels are translated via i18n.json (populated by sidebar:sync-i18n).
pnpm install
pnpm dev # defaults to fr + en
DEV_LOCALES=en pnpm dev # English only (faster)
DEV_PATH=web-cloud/web-hosting pnpm dev # scope to one subtree (fallback if dev shows a blank page)Blank page on every route? See README → Scoping to a route subtree (
DEV_PATH).
- Create or edit
.mdxfiles underdocs/{locale}/guides/...for each locale you're shipping. - Drop new images under
docs/public/images/{universe}/{product}/{guide-slug}/. - Wire the guide into config/sidebar/index.md.
pnpm sidebar:checkpnpm check(Biome lint + format with auto-fix).
- Build & dev-server setup → README.md
- Repo architecture → CLAUDE.md
- Migration history (Pelican → Rspress) → MIGRATION-TRIAGE.md
- Migration scripts (good reference for the exact format conversions applied) → scripts/migrate/