Document version management
SveltePress can keep the latest documentation at its normal URLs while publishing immutable historical snapshots below a version prefix such as /v/8.1/. Version management is opt-in: sites without a sveltepress.versions.json manifest behave exactly as before.
Install and initialize
Install the CLI beside the Vite plugin and theme:
pnpm add -D @sveltepress/cli Initialize version management with the version currently served by your site:
pnpm exec sveltepress versions init --current 8.1 --label "8.1" This creates sveltepress.versions.json. The Vite plugin discovers that file automatically; use sveltepress({ versions: false }) to disable discovery or sveltepress({ versions: { manifest: 'path/to/versions.json' } }) to select another manifest.
Enable incremental artifacts
Existing sites can migrate their committed snapshot directories once. New sites can run the same command immediately after versions init:
pnpm exec sveltepress versions migrate --site-id docs-example Migration replaces full src/routes/v/{id} copies with committed version-deltas/{id} source deltas and initializes a content-addressed page store under .sveltepress/version-artifacts. The store is a build cache; commit the source deltas and restore/cache the artifact store in CI.
Use the incremental command as the production build script:
{
"scripts": {
"build": "sveltepress versions build"
}
} versions plan reports compiled, reused, removed, and recomposed routes without building. versions build restores missing historical artifacts from committed deltas, compiles only changed current pages, composes the stable SveltePress shell, and then runs the normal Vite production build. A shell or index change may recompose routes without recompiling unchanged page content; a page compiler or artifact schema change intentionally invalidates every page artifact.
For GitHub Actions, restore the newest compatible store before the build and save the updated store under the current commit key:
- uses: actions/cache@v4
with:
path: .sveltepress/version-artifacts
key: sveltepress-pages-${{ runner.os }}-${{ github.sha }}
restore-keys: |
sveltepress-pages-${{ runner.os }}-
- run: pnpm build Use the equivalent persistent-cache facility on other CI providers. Do not share a store between different siteId values.
Generated page modules, including Default Theme LiveCode components, are embedded in their owning page artifacts. Cache only .sveltepress/version-artifacts; .sveltepress/live-code is a local development directory and is not required when a CI worker restores reusable pages.
LiveCode artifact self-check
This page dogfoods that behavior. The interactive component below is generated from this Markdown file, embedded in the page artifact, and server-rendered again after the local .sveltepress/live-code directory is removed. If the card renders and its button responds, both the reusable artifact and client hydration paths are working.
LIVE DOCUMENTATION CHECK
Artifact self-check passed
- Generated module embedded
- Server render complete
- Client hydration ready
<script>
let = (0)
const = [
'Generated module embedded',
'Server render complete',
'Client hydration ready',
]
</script>
<section ="artifact-check" data-version-artifact-live-code>
<div ="artifact-check__status" aria-hidden="true">✓</div>
<div ="artifact-check__content">
<p ="artifact-check__eyebrow">LIVE DOCUMENTATION CHECK</p>
<h3>Artifact self-check passed</h3>
<ul>
{#each as }
<li><span aria-hidden="true">✓</span>{}</li>
{/each}
</ul>
<button ="button" ={() => ++}>
Test interaction{ ? ` · ${}` : ''}
</button>
</div>
</section>
<style>
.artifact-check {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
overflow: hidden;
padding: 1.25rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 1rem;
background:
radial-gradient(circle at 100% 0%, rgb(255 94 122 / 18%), transparent 45%),
color-mix(in srgb, currentColor 4%, transparent);
}
.artifact-check__status {
display: grid;
width: 2.75rem;
height: 2.75rem;
place-items: center;
border-radius: 0.85rem;
color: #14231a;
font-size: 1.4rem;
font-weight: 800;
background: #70e19b;
box-shadow: 0 0 0 0.35rem rgb(112 225 155 / 12%);
}
.artifact-check__content h3,
.artifact-check__content p {
margin: 0;
}
.artifact-check__eyebrow {
color: #ff5e7a;
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.14em;
}
.artifact-check__content h3 {
margin-top: 0.15rem;
font-size: 1.2rem;
}
.artifact-check__content ul {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.85rem 0;
padding: 0;
list-style: none;
}
.artifact-check__content li {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.3rem 0.55rem;
border-radius: 999px;
font-size: 0.78rem;
background: color-mix(in srgb, currentColor 8%, transparent);
}
.artifact-check__content li span {
color: #45c97c;
font-weight: 800;
}
.artifact-check__content button {
padding: 0.55rem 0.8rem;
border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
border-radius: 0.65rem;
color: inherit;
font: inherit;
font-size: 0.85rem;
font-weight: 700;
background: transparent;
cursor: pointer;
}
.artifact-check__content button:hover {
border-color: #ff5e7a;
}
</style> Create a release snapshot
Start from clean, complete outgoing documentation. Build and create the next version before editing its pages or adding its :::since markers. create freezes the outgoing current version and makes the supplied ID current; only then should new documentation use that ID.
pnpm exec sveltepress versions build
pnpm exec sveltepress versions create 8.2 --label "8.2"
# 8.2 is now current: edit docs and add version="8.2" markers
pnpm exec sveltepress versions build
pnpm exec sveltepress versions validate Never run versions create over documentation that already contains the next version's edits. Those edits would be frozen into the outgoing version. If work started early, restore the known clean outgoing state, build and advance it, then reapply the edits to the new current version.
Multi-locale sites keep one versions manifest per locale (sveltepress.versions.json, sveltepress.versions.zh.json, sveltepress.versions.bn.json, …). Pass --locale <id> to init, create, validate, and other versions subcommands so they read and write that locale's manifest and deltas (for example version-deltas-zh/). Default locale commands omit --locale.
sveltepress versions build with no --locale drafts every locale, then composes /v/, /zh/v/, /bn/v/, … into one production output. Use --locale zh (optionally with --draft-only) only when you need a single-locale job.
sveltepress versions build --locale zh
sveltepress versions create 8.2 --label "8.2" --locale zh
sveltepress versions validate --locale zh See Internationalization for how locales and version bases compose (/zh/v, /bn/v).
Each locale freeze is independent: run build then create per locale (default, then --locale zh, --locale bn, …). A default-locale create must only capture unprefixed English routes — never src/routes/zh/ or src/routes/bn/. Locale creates write logical routes into version-deltas-<locale>/ (no double /zh/v/.../zh/... prefixes). If a freeze accidentally included sibling locale trees, recreate it with the locale-scoped CLI rather than hand-editing deltas.
create publishes the current draft manifest, writes only changed source pages and tombstones to version-deltas/8.1/, freezes route/sidebar/change metadata, moves 8.1 into history, and makes 8.2 current. It refuses stale drafts, duplicate IDs, symbolic links, a dirty Git worktree, and dependencies outside the frozen boundary. Use --allow-dirty only when the uncommitted state is intentionally the release source.
Published versions also receive a generated sourceHash. Each delta binds the frozen route, sidebar, and change catalog with a metadata hash. versions validate reconstructs every committed delta and checks both hashes, so source or metadata drift is detected even when the artifact cache is empty. Do not edit the hashes or delta files by hand.
The operation is atomic: a failed preflight does not leave a partial delta or update the manifest. versions list prints the version order, versions publish 8.1 prints the immutable manifest hash for CI publication, and versions gc --dry-run reports unreferenced local blobs before cleanup.
Manifest
{
"$schema": "./node_modules/@sveltepress/cli/schema/versions.schema.json",
"basePath": "/v",
"current": { "id": "8.2", "label": "8.2" },
"versions": [
{
"id": "8.1",
"label": "8.1",
"status": "deprecated",
"message": "Upgrade to 8.2 for fixes.",
"sourceRef": "v8.1.0",
"search": { "facetFilters": ["version:8.1"] }
}
],
"content": {
"include": ["**"],
"exclude": ["internal/**"],
"shared": ["$lib/**", "static/**"]
},
"artifacts": {
"mode": "incremental",
"siteId": "docs-example",
"store": ".sveltepress/version-artifacts",
"sources": "version-deltas"
}
} Version IDs are URL-safe lowercase identifiers and may contain dots or hyphens. include and exclude select route files to freeze. shared explicitly allows dependencies that remain live instead of being copied; keep this list narrow because a later change can affect every historical version.
Set status to deprecated or eol to show a prominent site-wide banner above the navigation. It warns that older-site functionality may be unavailable and links to the current version of the same logical page. sourceRef redirects historical edit links to a matching Git ref; set editLink: false to hide them. EOL versions default to noindex, and noIndex: true can opt another version out of indexing.
Navigation and missing pages
The Default Theme adds an accessible version selector automatically. Internal links and the frozen sidebar stay in the selected historical version when the target route existed there. Switching versions preserves the logical page when possible; otherwise it opens that version's home page and displays an explanatory notice.
Custom themes can import virtual:sveltepress/versions. It exposes the validated manifest plus resolveVersionContext, resolveVersionedPath, and resolveVersionSwitch helpers.
Browser code that imports these helpers directly from the package should use @sveltepress/vite/versioning/runtime; this entry contains no Node file-system code. Build and configuration code can continue to use @sveltepress/vite/versioning.
Search, PWA, and generated files
Built-in Local Search (Pagefind) keeps working on historical versions: production builds freeze each version's Pagefind assets via syncHistoricalPagefind, and the theme loads /v/{id}/pagefind/ (or the locale-prefixed equivalent) while you browse that snapshot. You do not need a search object on the version for Local Search.
DocSearch and custom search components still require an explicit search object on that historical version (for example indexName / facetFilters). Without it, the navbar shows that search is unavailable for the documentation version, so current remote results are not mistaken for historical docs. The Default Theme passes the selected version and search metadata to custom search components and merges configured DocSearch facet filters.
Version-aware builds also:
- emit canonical links for every page and
noindexfor configured versions; - include current and historical URLs in
sitemap.xml; - keep root
llms.txt/llms-full.txtcurrent-only and write historical files below/v/{id}/; - exclude historical HTML from PWA precaching and fetch it with a network-first strategy.
Treat version-deltas as immutable release source: review and commit it, but do not edit it by hand. The artifact store can be restored from those deltas on a cold CI worker, while a persistent CI cache avoids recompiling historical pages. Make corrections in current routes and create the next release delta.
Describe what changed
SveltePress compares the current route inventory with the most recent historical version in manifest order. A route that only exists in the current version is listed as a new page. Use page frontmatter to provide a focused summary or keep a page out of the change catalog:
---
title: What's new
versionChanges:
exclude: true
summary: Optional overview summary
--- For an existing Markdown page, mark only the important new section. The version, title, and page-unique stable ID are required:
### Hot reload
Existing documentation content.
:::since[Hot reload configuration]{version="8.2" id="hot-reload" summary="No restart required"}
New documentation content.
::: Unknown versions, duplicate IDs, unknown fields, and invalid field types stop development and production builds. New pages appear only under New pages; their since sections are not repeated under Updated pages. The first managed version has no comparison baseline and does not report the entire site as new.
Automatic "New" badges on pages, sections, and navigation
The Default Theme automatically marks newly introduced features across multiple navigation and content surfaces. Importantly, these badges are only shown while browsing the specific documentation version that introduced them; they automatically hide or adjust when switching to older or newer versions:
Page title badge (page level):
- When a page is newly added in the active version compared to the preceding baseline version (listed under
newPages), the Default Theme automatically displays an accent badge beside the main page title (h1.page-title), such asNew in 8.2. - The badge template defaults to
"New in {version}". Customize it viai18n.versionNewLabelin theme options (where{version}is replaced with the active version's label or ID). - When browsing subsequent versions, the page is no longer considered new, so the title badge is automatically omitted.
- When a page is newly added in the active version compared to the preceding baseline version (listed under
Section and paragraph badge (section level):
- In existing pages, sections wrapped in
:::since[Title]{version="8.2" id="unique-id" summary="..."}directives are rendered as stylized container boxes with an automatic version badge in their section header. - The section badge only displays when the user browses the exact version specified in
version="...". When browsing other versions, the badge is hidden. The text template also followsi18n.versionNewLabel.
- In existing pages, sections wrapped in
In-page navigation badge ("On this page" / TOC level):
- The right-side "On this page" table of contents automatically resolves associations between headings and
:::sincemarkers:- Headings contained inside a
:::sincemarker are associated directly; - When a
:::sincemarker contains no headings of its own, the Default Theme automatically associates it with the nearest preceding heading within the same Markdown container; - Multiple markers can belong to one heading.
- Headings contained inside a
- While browsing the version that introduced those changes, any associated heading anchor in the table of contents automatically renders a compact New badge (
VersionNavigationBadge). - The compact badge text defaults to
"New". Override it withi18n.versionNavigationNewLabel.
- The right-side "On this page" table of contents automatically resolves associations between headings and
Sidebar navigation badge (sidebar level):
- The sidebar automatically adds the compact New badge to both new pages (
newPages) and updated pages (updatedPages), also configured viai18n.versionNavigationNewLabel.
- The sidebar automatically adds the compact New badge to both new pages (
What's New overview page
In addition to contextual badges on pages, sections, and navigation, you can embed the full release change catalog anywhere on your site:
<script>
import from '@sveltepress/theme-default/VersionChanges.svelte'
</script>
< /> See the official site's live catalog on the What's new page .
VersionChanges defaults to the documentation version resolved from the current page URL. A valid ?version={id} explicitly overrides that context. Each version's catalog is generated against its immediately preceding version in manifest order, while frozen historical catalogs stay independent of later current documentation. Current links stay unprefixed; historical links target /v/{id}/..., including exact section anchors.
Custom themes can read the same immutable data from virtual:sveltepress/versions:
import {
,
,
} from 'virtual:sveltepress/versions'
const = ()
const = ('8.1') versions create freezes the outgoing current change set in its immutable artifact manifest and source delta. Historical catalogs are always read from frozen metadata rather than reconstructed from later current documentation, and versions validate detects malformed markers, invalid references, duplicate anchors, corrupt artifacts, and delta drift.