Versioning
Freeze snapshots of your docs per release with a version switcher, canonical-to-latest SEO, version-scoped search, and a version-aware agent surface.
Blume versions your docs the way releases actually work: the latest documentation lives at your content root with clean, unprefixed URLs, and each past version is a frozen snapshot in its own folder. Cut a snapshot when you ship, and Blume wires up the switcher, the “old version” notice, search scoping, SEO, and the agent surface for you. It’s opt-in: without a versions block, nothing changes.
Enable it
Add a versions block naming the current docs and any archived snapshots:
versions: {
current: { label: "v2.0", badge: "Latest" },
archived: [
{ id: "v1.0" },
{ id: "v0.9", label: "0.9 (legacy)" },
],
}
current labels the unprefixed tree in the switcher (with an optional badge). Each archived entry’s id is both the snapshot’s directory name and its URL segment — ids must start with a letter (v1.0, not 1.0) so they can never collide with numeric ordering prefixes. List archived versions newest first; that order is the switcher order.
Cut a version
When you release, freeze the current docs with one command:
blume version v1.0
This copies your content tree into docs/v1.0/ (existing snapshots excluded), rewrites root-absolute links inside the copy so they stay within the snapshot (/guides/x becomes /v1.0/guides/x, fenced and inline code untouched), and registers the id in blume.config.ts — or prints the entry to paste when your config is shaped in a way it won’t touch. Links to pages that aren’t part of the copied tree — generated API references, remote sources like a changelog — keep pointing at the live pages, since the snapshot has no copy of them. Run blume version with no id to list the configured versions.
Review and commit the new directory like any other content. Restart blume dev to pick it up.
docs/
index.mdx -> / (latest)
guides/quickstart.mdx -> /guides/quickstart
v1.0/
index.mdx -> /v1.0 (frozen)
guides/quickstart.mdx -> /v1.0/guides/quickstart
Archived means frozen. Future edits belong in the live tree; a snapshot is the docs as they were. Blume leans on that: snapshots keep their own folder meta and translations, blume translate never retranslates them, and a configured explicit sidebar applies only to the current docs — a snapshot’s sidebar always comes from its own files.
The switcher and the notice
With versions configured, the header grows a version dropdown automatically. Switching lands on the same page in the target version when it exists, and on that version’s root when it doesn’t (set switcher.redirect: "root" to always land on the root). If you declare your own kind: "version" selector in navigation.selectors, it replaces the automatic one.
Every archived page also shows a non-dismissible notice with a “Go to latest” link pointing at the page’s live equivalent. Customize or disable it per version:
archived: [
{ id: "v1.0", banner: "These docs cover the 1.x SDK." },
{ id: "v0.9", banner: false },
];
SEO
Old docs are search engines’ favorite trap: the stale page outranks the live one, or both compete. Blume defaults to the answer SEO guides recommend and no other docs framework automates — archived pages stay indexable but declare the latest equivalent as their canonical, so the live page is authoritative while version-only content (a page that no longer exists in the latest docs) remains findable with a self-canonical.
Per version you can pick a different treatment:
archived: [
{ id: "v1.0" }, // canonical → latest (default)
{ id: "v0.9", canonical: "self" }, // every page authoritative
{ id: "v0.8", noindex: true }, // deindexed entirely
];
The sitemap follows suit: archived pages whose canonical points at a live equivalent are left out, noindex versions are left out wholesale, and version-only pages stay listed. A page’s own seo.canonical frontmatter always wins.
Search
The search dialog scopes results to the version being viewed, with an “All versions” toggle (remembered per reader) beside the language one. Cross-version hits name their version on the result row. Orama (the default), FlexSearch, Algolia, and Typesense all honor the scoping — hosted records carry a version facet, with the current docs uploaded as "current" — while Pagefind stays unscoped, matching its locale behavior.
Agents
The agent surface is version-aware — something no other docs framework does:
- The MCP
search_docsandlist_pagestools default to the current docs and acceptversion: an archived id ("v1.0") or"all".get_navigationreturns an archived snapshot’s tree on request. llms.txtsections archived versions after the current docs, labeled1.0 (archived), so an agent reading the index knows which docs are frozen.llms-full.txtstays current-only — the flat dump never interleaves frozen copies of the same page.- Raw Markdown mirrors (
.mdURLs) exist for every version’s pages, as for any route.
With i18n
Versioning composes with internationalization. On disk the version folder is outermost — a snapshot naturally contains its locale folders — while in URLs the locale stays outermost, matching the rest of the site:
docs/
guides/x.mdx -> /guides/x
fr/guides/x.mdx -> /fr/guides/x
v1.0/
guides/x.mdx -> /v1.0/guides/x
fr/guides/x.mdx -> /fr/v1.0/guides/x
Locale fallback works within each version: an untranslated snapshot page renders the fallback locale’s content at the localized URL, and hreflang alternates group per version. A version id may not collide with a configured locale code — Blume rejects that config outright.
What stays unversioned
Versioning covers the docs content tree. The blog, changelog, API references generated from OpenAPI specs, and custom pages are always current. Two more behaviors worth knowing: header tabs are defined against the current docs, so inside an archived tree the sidebar renders unscoped by tabs; and large sites should note each snapshot is a full copy — content, search index entries, and navigation data all grow per version.