Skip to content
duxt

Several versions

Serve the same documentation at several refs, with a switcher and a banner.

A ref becomes a version. Point a source at tags and each one is published, with a version segment in the URL and an entry in the switcher.

Steps

  1. List the refs. A bare string is a branch. A tag has to say so — git keeps the two in separate namespaces, and asking for a tag under refs/heads fails the build.
    sources: [
      {
        repo: 'acme/api',
        path: 'docs',
        refs: [
          { branch: 'main', status: 'upcoming' },
          { tag: 'v2.0.0' },
          { tag: 'v1.4.0' },
          { tag: 'v0.9.0', status: 'eol' }
        ]
      }
    ],
    sourceOptions: { defaultRef: 'v2.0.0' }
    
  2. Name the default. defaultRef is the ref served without a version prefix and the one search engines are pointed at. Left unset it is the first in the list — which is rarely what you want when the list starts at a branch.
  3. State the lifecycle where it cannot be derived. Semver orders tags, so "older than the default" needs no help. It cannot place a branch against a tag: main is upcoming, and without saying so its reader is told to upgrade to a version older than what they are reading.
  4. Use latest if you do not want to edit this list every release. It resolves at build time to the newest semver tag of that repository.
    refs: [{ branch: 'main', status: 'upcoming' }, 'latest']
    
  5. Mark a page's version requirement in the prose, where a reader looking at one version cannot see the others:
    ### Retries :since{version="v2.0.0"}
    

How coarse a version is

The name is the site's choice, not the mechanism's, and it is the decision that matters most here — it is what every link to your documentation will carry.

A line per version, as a branch. What Laravel, Symfony and Django do: 11.x, 10.x, 9.x are branches that keep moving, and a patch release changes nothing about the URL.

sources: [
  {
    path: 'docs',
    refs: [
      { branch: '12.x', status: 'upcoming' },
      '11.x',
      { branch: '10.x', status: 'deprecated' },
      { branch: '9.x', status: 'eol' }
    ]
  }
],
sourceOptions: { defaultRef: '11.x' }

A bare string is a branch; only a tag has to say so.

A line per version, from tags. Where the release you want to publish is a tag but the URL should not move with every patch, label renames it:

refs: [
  { tag: 'v2.4.1', label: 'v2' },
  { tag: 'v1.9.3', label: 'v1', status: 'deprecated' }
]

Every release its own version. { tag: 'v2.4.1' } with no label. Each is citable forever, and the switcher grows by one entry per release — which is the right answer for a specification and the wrong one for a manual.

The same choice applies to a section that names its own versions: version: 'v2' or version: '2.4.1', with exactly the same trade-off. One model, one decision, whether the content comes from a ref or from a file.

When the versions are not refs

A version is normally a checkout. Two shapes are not, and both are ordinary:

Documentation in folders. docs/v1 beside docs/v2 in one repository, no tags. Each source names the version it is:

sources: [
  { path: 'docs/v2', version: 'v2' },
  { path: 'docs/v1', version: 'v1', status: 'deprecated' }
]

An API in files. openapi/v1.yaml beside openapi/v2.yaml — the usual way an API is kept, and the versions belong to the section, not to the source:

generated: [
  {
    type: 'openapi',
    label: 'API',
    path: 'openapi/v2.yaml',
    versions: [
      { version: 'v2', path: 'openapi/v2.yaml' },
      { version: 'v1', path: 'openapi/v1.yaml', status: 'deprecated' }
    ]
  }
]

Both produce exactly what refs produce: a segment in the URL, an entry in the switcher, the banner and the canonical. The switcher stays scoped to the artefact you are reading — on /api it offers the API's versions, on a prose page the documentation's.

What happens to the old versions

A non-default version carries noindex and a canonical pointing at the same page in the default one, and an eol version leaves the sitemap entirely. That is what stops a search engine offering v0.9.0 where the reader wanted today's documentation — see URLs and versions.

Checklist

  • Every tag is written as { tag: … }, not as a bare string
  • The version names are as coarse as the project's own support lines
  • defaultRef names the version readers should land on
  • A branch in the list carries an explicit status
  • The switcher shows every version, with a badge on the dead ones
  • A dead version's page is absent from /sitemap.xml
  • Where the versions are folders or files rather than refs, exactly one of refs, a source's version and a section's versions is in play
Was this page helpful?