Docs sites for long-lived products eventually need to version themselves. NookDocs versioning is URL-prefix based — your default version serves at /, other versions at /<slug>/... — and content lives side-by-side in the same repo under folders that match the slugs.
Two questions, one answer
How do readers switch?
A dropdown in the navbar shows every non-hidden version. Click → the URL changes to that version's prefix. Active version is inferred from the URL on every page load.
Where does the content live?
Alongside your existing docs, under folders named after each version's slug. The default version has no folder prefix — it's the root of your docs tree.
Declare versions
Open Configurations → Versions in the dashboard (or edit nookdocs.config.json directly):
{
"versions": [
{ "version": "v3", "default": true, "tag": "Latest", "status": "stable" },
{ "version": "v2", "status": "stable" },
{ "version": "v1", "tag": "Deprecated", "status": "deprecated" }
]
}Exactly one version may carry default: true. Validation rejects configs with zero or multiple defaults.
Each entry's URL slug is the version label lowercased (override with slug: "..." if you want something custom). The default version's URL has NO prefix — it serves at /. Non-default versions serve at /<slug>/....
Repo layout
This is the layout for the config above:
your-repo/ # the docs base
├── nookdocs.config.json
├── introduction.mdx ← v3 (default), no prefix
├── quickstart.mdx
├── guides/
│ └── authentication.mdx
├── v2/ ← v2, URL prefix /v2
│ ├── introduction.mdx
│ ├── quickstart.mdx
│ └── guides/
│ └── authentication.mdx
└── v1/ ← v1, URL prefix /v1
├── introduction.mdx
└── guides/
└── authentication.mdxURLs resolve like this:
| URL | Reads from |
/ | introduction.mdx (v3) |
/guides/authentication | guides/authentication.mdx (v3) |
/v2/ | v2/introduction.mdx |
/v2/guides/authentication | v2/guides/authentication.mdx |
/v1/ | v1/introduction.mdx |
The sync pipeline treats v2/ as a peer of the base content — same MDX rules, same component allowlist, same frontmatter fields. There's no "v2 mode" to toggle.
Navigation per version
The current version of NookDocs renders the top-level navigation config for every version's sidebar. Per-version navigation trees (so the v2 sidebar can differ from v3) is queued for a follow-up sprint — see the Navigation V2 roadmap.
For now, your navigation is shared across versions. Pages that only exist in one version simply 404 on the others — use hidden: true in frontmatter to drop a page from the sidebar without removing the file.
Writing tips
Add a new version
Snapshot the current docs
Copy the base content into a v2/ folder at the base. The old version's content is frozen; new work continues at the base.
Mark the old version in config
Add { "version": "v2", "status": "stable" } to versions[] — alongside the existing default entry.
Commit + push
The sync pipeline ingests the new folder. Visitors at /v2/... see the snapshot immediately.
Deprecate an old version
Edit the version's entry to add a red tag and a deprecation banner on the version's pages:
{
"version": "v1",
"tag": "Deprecated",
"status": "deprecated"
}Status deprecated colours the badge red in the navbar dropdown. For a visible warning on every v1 page, add a frontmatter banner to the v1/*.mdx files or use a Banner component at the top of each doc.
Retire a version
Three options depending on how clean you want the break:
Hide from the switcher: set
hidden: trueon the entry. URLs still resolve, but the dropdown doesn't show v1. Good for "read-only legacy, don't surface it."Redirect to the latest: add a
redirectsentry for/v1/*→/*. The old URLs bounce to the current version.Delete: remove the
v1/folder and the versions[] entry. Returns 404 on/v1/...URLs. Add a 404 redirect (see Redirects) if you want a softer landing.
OpenAPI per version
API docs usually track their own version independently. Your api.openapi can point at different specs per version by putting the spec file next to each version's content:
your-repo/ # the docs base
├── openapi.json ← v3 spec (default)
├── v2/
│ └── openapi.json ← v2 spec
└── v1/
└── openapi.json ← v1 specAnd reference them in the version-scoped navigation (once per-version nav ships). Until then, use a single top-level api.openapi that points at the latest, and surface legacy specs as direct download links inside the deprecated version's docs.
AI Assistant + versioning
The chat widget's RAG retrieval (via Postgres FTS) indexes every page in the project, including version-prefixed ones. A user asking "how do I authenticate" will get results from whichever version matches best — not necessarily the active one.
Two mitigations:
Pre-pend the active version to queries (planned) — the widget will detect
/v2/in the URL and scope retrieval todocs/v2/*.Disable the widget on deprecated versions:
hidden: trueon the version entry +config.ai.assistant.enabled: falseif the version is hosted separately.