Adding Versioned Docs to Docusaurus

When an internal API or platform ships breaking changes, engineers consuming an older release need documentation that matches the version they run — not the bleeding edge. Docusaurus has first-class versioning that snapshots your docs at each release so multiple versions are served side by side. This how-to walks through cutting versions, configuring the version dropdown, and keeping CI healthy. It builds on Docusaurus Setup & Customization within the Developer Portal Architecture & Frameworks strategy.

Prerequisites

  • Node.js 20+ and an existing Docusaurus 3.x site (@docusaurus/core 3.5.0+).
  • The classic preset configured with a docs instance and a working sidebars.js.
  • A Git repository with a branching/tagging convention for releases.
  • Current, unversioned content under docs/ that builds cleanly with npm run build.
# Requires @docusaurus/core >= 3.5.0
npx docusaurus --version   # 3.5.x

Exact Configuration

  1. Cut your first version snapshot. This copies the current docs/ into versioned_docs/version-1.0/, snapshots the sidebar, and appends the label to versions.json. After this, docs/ becomes the “next”/unreleased version.

    # Requires @docusaurus/core >= 3.5.0
    npm run docusaurus docs:version 1.0
    # Creates: versioned_docs/version-1.0/, versioned_sidebars/, versions.json
    
  2. Configure how versions are labeled and routed in docusaurus.config.js. Setting lastVersion and an explicit versions map controls which version is the default and what each is called in the UI.

    // docusaurus.config.js — requires @docusaurus/preset-classic >= 3.5.0
    presets: [
      ['@docusaurus/preset-classic', {
        docs: {
          sidebarPath: require.resolve('./sidebars.js'),
          lastVersion: '1.0',            // 1.0 is the default served at /docs
          versions: {
            current: { label: 'Next (unreleased)', path: 'next' },
            '1.0': { label: 'v1.0', path: '1.0' },
          },
          onlyIncludeVersions: ['current', '1.0'],
        },
      }],
    ],
    
  3. Add the version dropdown to the navbar so readers can switch releases. The docsVersionDropdown item renders automatically from versions.json.

    // docusaurus.config.js — themeConfig.navbar.items
    themeConfig: {
      navbar: {
        items: [
          { type: 'docsVersionDropdown', position: 'right' },
        ],
      },
    },
    
  4. Cut subsequent versions on each release. Run the version command from CI when you tag a release so the snapshot is deterministic and reviewed.

    # On release tag v2.0
    npm run docusaurus docs:version 2.0
    
  5. Prune old versions to control build cost. Versioning multiplies your page count, which compounds the build-time concerns covered for large sites in Optimizing static site generation for 10k+ pages. Drop end-of-life versions from onlyIncludeVersions and delete their versioned_docs directory.

Validation

# 1. Confirm the version snapshot and registry exist
cat versions.json                 # Expected: ["1.0"] (then ["2.0","1.0"] later)
ls versioned_docs/                # Expected: version-1.0/ (and version-2.0/)

# 2. Build the full site including all versions
npm run build
# Expected: exit 0; "Success! Generated static files in 'build'."

# 3. Confirm versioned routes were emitted
ls build/docs/1.0/ && ls build/docs/next/
# Expected: HTML output present under each version path

# 4. Catch any broken cross-version links
npm run build 2>&1 | grep -iE "broken link" || echo "no broken links"
# Expected: "no broken links"

A healthy result: versions.json lists every active version, the build succeeds, and each version path renders under build/docs/.

Edge Cases & Troubleshooting

Symptom Root Cause Resolution
Version dropdown missing from navbar docsVersionDropdown item not added Add the item to themeConfig.navbar.items
Old version shows new content Edited versioned_docs/version-X instead of docs/ Edit docs/ for unreleased changes; only patch snapshots intentionally
Build time doubled after versioning Every version rebuilt on each run Limit onlyIncludeVersions to actively supported releases
Broken links between versions Hardcoded absolute /docs/... paths Use relative links or the @site alias so links resolve per version
docs:version overwrote a sidebar Re-ran versioning for an existing version Versions are immutable; delete the snapshot before re-cutting

Frequently Asked Questions

How many versions should I keep live?

Keep the current/next version plus the releases you actively support — typically two or three. Each version is a full copy of the docs, so build time and bundle size grow linearly; archive end-of-life versions by removing them from onlyIncludeVersions.

Can I edit an already-released version’s docs?

Yes, by editing the files under versioned_docs/version-X/ directly, but treat that as a deliberate patch to a frozen release. Day-to-day edits belong in docs/, which represents the unreleased “next” version until you cut the next snapshot.