Versioning

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Docusaurus 3.10.2 (the last release of the 3.x line) and checked on 2026-10-26. It explains how the docs plugin freezes a copy of the documentation as a numbered version, which files and routes that creates, how to control which version is the default, how to hide or limit versions, and why a site should keep only a few of them. The running example is the documentation site of Pocket Units, the small units-conversion library with a C# and a Python implementation. A small site was built with Docusaurus 3.10.2 for this page and every command, error message and generated route below was read from its output.

What versioning is and why it exists

A versioned documentation site lets a reader of release 1.0 keep reading the pages that described release 1.0 while the project moves on. Docusaurus does this by copying files, not by reading Git history: when you run a command, the current contents of the docs folder are duplicated into a new folder, and from then on that folder is an independent set of documents with its own sidebar and its own URLs.

This has costs that the official guide stresses. Every version is built, so build time and the size of the site grow. Every fix has to be repeated in each version it applies to, and contributors have to know which folder to edit. The guide recommends that most sites do not use versioning at all, and keeps it for sites with high traffic and rapid changes to the documentation between versions. A site whose documentation rarely changes, or that only documents the newest release, is better served without versions. The rest of this page assumes you decided that you need them.

Current, latest and the other versions

Two words describe different things and are easy to confuse.

  • The current version is defined by the file system: it is whatever is in the docs folder. The docs plugin gives it the name current. By default its label is Next, and it is served under the path next.

  • The latest version (also called the last version) is defined by navigation: it is the version that navbar items and the plugin root link to, and by default it is served at the root of the docs route, with no version segment. It is the first entry of versions.json.

With the default configuration they are different versions: the current folder holds work in progress for the next release (served under next) and the newest frozen version is the one most readers see. The configuration can also make them the same version, as described in Choosing the latest version.

The folder layout

After the first version is created, a site has the following layout. The docs and sidebars file belong to the current version, each folder under versioned_docs is a frozen version, and versions.json is the list that ties them together.

website/
  docs/                              current version
  sidebars.ts                        sidebar of the current version
  versions.json                      version names, newest first
  versioned_docs/
    version-2.0/                     frozen documents of 2.0
    version-1.0/                     frozen documents of 1.0
  versioned_sidebars/
    version-2.0-sidebars.json        frozen sidebar of 2.0
    version-1.0-sidebars.json
Folders on disk mapped to the current, latest and an older version and to their URLs

The figure shows how each folder maps to a version and a URL when the docs are served at the site root, as in the example of this page. By default the docs are served under a docs route base path, so the URLs gain a leading /docs. The versions.json file is a plain JSON array of version names ordered from newest to oldest; the plugin treats its first entry as the latest version unless lastVersion says otherwise. When the docs plugin is registered more than once with an id, the folders and the file are prefixed with that id (for example community_versioned_docs); the default plugin has no prefix.

Creating a version with docs:version

The command takes one required argument, the version name:

npx docusaurus docs:version 1.0

Before running it, make sure the docs folder is in the state that should be frozen. The command does three things:

  1. copies the full contents of docs into versioned_docs/version-<name>;

  2. writes the sidebar that is configured at that moment into versioned_sidebars/version-<name>-sidebars.json;

  3. adds the name at the top of versions.json.

In the Pocket Units example the first release documented lengths. The current documents were docs/intro.md and docs/convert.md, and the sidebar was a single autogenerated category:

sidebars.ts
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';

const sidebars: SidebarsConfig = {
  docs: [{type: 'autogenerated', dirName: '.'}],
};

export default sidebars;

Running the command printed one line and created three paths:

npx docusaurus docs:version 1.0
[SUCCESS] [docs]: version 1.0 created!
versioned_docs/version-1.0/convert.md
versioned_docs/version-1.0/intro.md
versioned_sidebars/version-1.0-sidebars.json
versions.json

The versioned sidebar file is the same sidebar written as JSON. Because the sidebar is autogenerated, it keeps working inside the version folder, where dirName: '.' is relative to the content root of that version:

versioned_sidebars/version-1.0-sidebars.json
{
  "docs": [
    {
      "type": "autogenerated",
      "dirName": "."
    }
  ]
}

The version 2.0 release added mass conversion. After editing docs/intro.md and adding docs/mass.md, a second run froze the new state. The versions.json file then read, newest first:

[
  "2.0",
  "1.0"
]

Further edits of docs (here a line about temperatures under development) now belong to the current version and do not change either frozen copy.

Errors of the command

The command validates its input. These were the messages observed:

Situation Result

Name already in versions.json (docs:version 2.0 run twice)

Error: [docs]: this version already exists! Use a version tag that does not already exist.

Name containing a slash (docs:version a/b)

Invalid version name "a/b": version name should not include slash (/) or backslash (\).

No argument

error: missing required argument 'version'

The command did not reject the name current: running it created a version with that name, which is also the name the plugin uses for the docs folder, so avoid it. A name is any string without slashes; semantic versions such as 1.0.0 and short names such as 2.0 both work, and the name becomes the default URL segment and label.

Several docs plugin instances

If the site registers an additional docs plugin with an id, the command has an id-suffixed form, docs:version:<id>, and the folders it uses are prefixed with the id. This page does not run a second instance; see Plugins and presets for how plugin ids work.

Editing, adding and deleting versions

Each folder in versioned_docs is served by its own routes, so changing a file there affects only that version. This is how a typo in release 1.0 is fixed after 2.0 has shipped: edit the file under versioned_docs/version-1.0, commit and publish.

  • To add a document to a frozen version, create the file in its version folder and add the reference to the matching file in versioned_sidebars. Sidebar paths in a versioned file are relative to the content root of that version.

  • To add a document to the current version, put it in docs and reference it from sidebars.ts (or let the autogenerated sidebar find it).

  • To delete a version, remove its entry from versions.json, delete its versioned_docs folder and delete its versioned_sidebars file. All three steps are needed.

A versioned sidebar that points to a document the version does not contain stops the build. With the sidebar of version 1.0 edited to list a mass document that only exists from 2.0 on, the build reported:

[ERROR] Loading of version failed for version 1.0
Error: Invalid sidebar file at "versioned_sidebars/version-1.0-sidebars.json".
These sidebar document ids do not exist:
- mass

Available document ids are:
- convert
- intro

Configuring the versions

All of the options below belong to the docs plugin and can be set in the preset options (the usual place) or in the plugin options. The Pocket Units site serves the docs at the root with routeBasePath: '/', so the URLs are short.

Option Default Meaning

lastVersion

First entry of versions.json

The version navbar items and the docs root link to, and the one served without a version segment.

includeCurrentVersion

true

Whether the docs folder is published as a version. Turn it off while the next release is unfinished.

onlyIncludeVersions

All versions

A subset of the names in versions.json (plus current) to build.

disableVersioning

false

Ignore the frozen versions and publish only the current one. It is an error together with includeCurrentVersion: false.

versions

{}

A map from version name to per-version options.

The per-version options inside versions are these:

Key Meaning

label

Text shown in the dropdown, badge and banner.

path

Route segment of the version, appended to the base URL and the route base path. By default the latest version has none and current has next.

banner

'none', 'unreleased' or 'unmaintained'. By default a version above the latest is unreleased and a version below it is unmaintained.

badge

Show a badge with the version label at the top of each page of the version.

noIndex

Ask search engines not to index the version.

className

Extra class on the html element of the pages of the version, for version-specific CSS.

A configuration that labels the unreleased work as 3.0, marks the old release as unmaintained and keeps it out of search engines looks like this:

docusaurus.config.ts
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';

const config: Config = {
  title: 'Pocket Units',
  url: 'https://docs.example.com',
  baseUrl: '/',
  onBrokenLinks: 'throw',
  presets: [
    [
      'classic',
      {
        docs: {
          routeBasePath: '/',
          sidebarPath: './sidebars.ts',
          versions: {
            current: {label: '3.0 (next)', banner: 'unreleased'},
            '1.0': {banner: 'unmaintained', noIndex: true},
          },
        },
        blog: false,
      } satisfies Preset.Options,
    ],
  ],
  themeConfig: {
    navbar: {
      title: 'Pocket Units',
      items: [{type: 'docsVersionDropdown', position: 'right'}],
    },
  } satisfies Preset.ThemeConfig,
};

export default config;

The build produced these routes (a page of the current version, the latest version and the old one):

build/index.html              2.0, the latest version, at the root
build/convert/index.html
build/mass/index.html
build/next/index.html         current, labelled 3.0 (next)
build/next/convert/index.html
build/next/mass/index.html
build/1.0/index.html          frozen 1.0, with the unmaintained banner
build/1.0/convert/index.html

In the generated HTML the banner of version 1.0 says that it is documentation for Pocket Units 1.0, no longer actively maintained, and links to the latest version (2.0). The banner of the current version says that it is unreleased documentation for 3.0 (next) and links to the latest version as well. The latest version shows no banner. A version setting noIndex: true adds a robots meta tag with noindex, nofollow to its pages and removes its routes from the generated sitemap: the sitemap of the example listed the root and next routes but none under /1.0/.

The badge option is shown on each page of a version; in the example site every version already displayed a badge (for instance Version: 2.0 on the latest) because more than one version existed, so set it explicitly only when you want to control it. The className option put the given class on the html element, next to the classes the theme adds, which makes per-version CSS possible.

Choosing the latest version

The default, where the frozen 2.0 is the latest and docs is Next, fits a project that releases 2.0 and starts working on 3.0 straight away. A project that releases 1.0 and will maintain it for a while before starting on 2.0 can avoid keeping the same content in two folders by pretending that the current version is the cut one. Set lastVersion to current and give the current version a label:

docusaurus.config.ts (docs options only)
docs: {
  routeBasePath: '/',
  sidebarPath: './sidebars.ts',
  lastVersion: 'current',
  versions: {
    current: {label: '3.0'},
  },
},

With that setting the docs folder was served at the site root and the frozen versions moved to their own segments:

build/index.html              current, now the latest version
build/convert/index.html
build/mass/index.html
build/2.0/index.html          frozen 2.0
build/2.0/convert/index.html
build/2.0/mass/index.html
build/1.0/index.html          frozen 1.0
build/1.0/convert/index.html

This suits a project whose docs folder tracks one major version that is constantly patched. Because the root route is now taken by the current version, a link to / keeps working. A first attempt that also gave the current version the path next while making it the latest left nothing at the root, and the build then failed with broken links to / (the navbar logo) because onBrokenLinks was throw.

Limiting the built versions

The onlyIncludeVersions option is a list of names. Besides frozen versions it accepts current. The official guide suggests limiting a development server or a deploy preview to two or three versions to shorten startup and build time. With ['current', '2.0'] the example built the root (2.0) and next and skipped 1.0 completely: the dropdown listed only 3.0 (next) and 2.0, and no /1.0/ route existed.

If the latest version is not in the list, Docusaurus picks another one. With ['current', '1.0'] the root of the site served version 1.0, because the first included frozen version became the latest. Writing both options so that they disagree is rejected:

Invalid docs option "lastVersion": if you use both the "onlyIncludeVersions" and
"lastVersion" options, then "lastVersion" must be present in the provided "onlyIncludeVersions" array.

With ['current'] only, the current documents became the root of the site, which is a convenient state for writing. A common pattern is to turn the option on from an environment variable, so the full site still builds in continuous integration:

docusaurus.config.ts (docs options only)
// Build only the unreleased docs while writing: DOCS_FAST=1 npm run start
const fast = process.env.DOCS_FAST === '1';

// inside the preset options:
docs: {
  routeBasePath: '/',
  sidebarPath: './sidebars.ts',
  onlyIncludeVersions: fast ? ['current'] : undefined,
},

Both modes were built: the full build produced the eight routes listed earlier, and DOCS_FAST=1 produced only index, convert and mass at the root.

includeCurrentVersion: false publishes only the frozen versions. In the example the build then had 2.0 at the root and 1.0 under /1.0/ and no next routes, and the dropdown listed 2.0 and 1.0. If the versions option still describes current, the build stops with Invalid docs option "versions": unknown versions (current) found. Available version names are: 2.0, 1.0, so remove that entry at the same time.

The version dropdown

The classic theme provides the navbar item types docsVersionDropdown (a dropdown of all versions) and docsVersion (a link to the version being browsed). They choose the target in order: the version of the page the reader is on, then the version the reader last viewed, then the latest version. When switching versions from the dropdown, the reader stays on the same document as long as its ID exists in the other version, which is another reason to keep document IDs stable.

The dropdown accepts these fields:

Field Meaning

position

left or right side of the navbar.

versions

An array of names to show in a given order, or an object mapping a name to {label} to rename entries.

dropdownItemsBefore, dropdownItemsAfter

Extra link items at the start or end of the list.

dropdownActiveClassDisabled

Do not mark the item as active while browsing docs.

docsPluginId

The id of the docs plugin the versions belong to (default when omitted).

The dropdownItemsAfter field is the supported way to link versions that are no longer built, such as an archived copy of an old release hosted elsewhere:

docusaurus.config.ts (navbar only)
items: [
  {
    type: 'docsVersionDropdown',
    position: 'right',
    dropdownItemsAfter: [
      {href: 'https://archive.example.com/pocket-units/0.9/', label: '0.9 (archive)'},
    ],
  },
],

In the example the full build rendered the entries 3.0 (next), 2.0, 1.0 and 0.9 (archive), and the DOCS_FAST=1 build rendered 3.0 (next) and 0.9 (archive). The dropdown is generated from the versions that are built, not from versions.json, so hiding a version with onlyIncludeVersions also hides it from the dropdown.

Keeping versions few

The official guide gives practical rules that follow from the file-copy model.

  • Version only when the documentation really differs. A patch release that fixes a bug does not change what the docs say, so cutting 1.0.1 after 1.0.0 only duplicates files. Semantic versioning says patch releases add no features, so version at the minor or major level at most.

  • Keep the number of versions below about ten. Old versions nobody reads still cost build time and maintenance. The guide cites the Jest site, which maintains only the latest few versions of its documentation.

  • Archive instead of keeping everything. A host that stores each production build under an immutable URL (the guide names Netlify as an example) lets you link an old version from the dropdown with dropdownItemsAfter and remove it from versions.json. Docusaurus' own site lists archived versions this way.

  • Link documents by relative file path with the extension, for example [the conversion guide](./convert.md), so that Docusaurus resolves the link to the matching version. The mass page of the example linked to convert.md this way and the link resolved inside next, the root and the frozen copy; with onBrokenLinks: 'throw' a link that cannot be resolved in one version fails the build.

  • Import shared React components with the @site alias (@site/src/components/Foo) rather than a relative path, because the nesting level of a document changes when it is copied under versioned_docs.

  • Decide whether assets are per version or shared. Assets that must match a version live next to its documents and are referenced with relative paths. Global assets live in static and are referenced with absolute paths.

Search and versions

With Algolia DocSearch the theme option contextualSearch is on by default and restricts results to the current locale and version. See Search for the setup. Without contextual search a reader of 2.0 sees matches from every version, which are often near duplicates.

Versions and translations

Each version is translated separately: the French copy of version 1.0 lives under the i18n folder and has to be kept in step with the English one, so every extra version multiplies the translation work. See Internationalization for the folder layout.

Version snapshots

The figure above shows where files live. The diagram below shows what happens over time: every run of the command copies the state of docs at that moment, and later edits go to docs only.

flowchart LR D0["docs/ at release 1.0"] -->|"docs:version 1.0"| V1["versioned_docs/version-1.0"] D0 --> D1["docs/ at release 2.0"] D1 -->|"docs:version 2.0"| V2["versioned_docs/version-2.0"] D1 --> D2["docs/ next, unreleased 3.0"] V1 -.->|"typo fix only"| V1 D2 --> S["site: / = 2.0, /next = 3.0, /1.0 = 1.0"] V2 --> S V1 --> S

In the diagram the solid arrows show the history of the working copy and the copies made by the command, and the dashed loop shows that a frozen version is edited in place. The three versions together make the site.

Pitfalls and version notes

Symptom Cause and fix

A new feature appears only in Next

The page was edited in docs, which is the unreleased version. Frozen versions never change unless their own folder is edited.

A fix must be applied several times

Each version is a separate copy. Edit each affected folder, or keep fewer versions.

The build fails after a sidebar change in a frozen version

The versioned sidebar names a document ID the version does not contain. Compare it with the files in that version folder.

Imports break after versioning

Relative imports changed depth when copied into versioned_docs. Use the @site alias.

Root of the site is a 404

The latest version was given a path of its own, or the version the root pointed to is not built. Check lastVersion, path, onlyIncludeVersions and versions.json.

Old versions appear in search engines

Set noIndex: true on the version, or archive it elsewhere.

Edit links point to the wrong place

Edit links of frozen pages lead to the old file. Set editCurrentVersion: true to send every edit link to the current document instead (ignored when editUrl is a function).

The behaviour above was observed with 3.10.2, the last release of the 3.x line.