Content
Dimensions
Run one site across a matrix of versions or locales. Each axis prefixes the slug, the outline is built per coordinate, and an untranslated page falls back to the default instead of answering 404.
On this page
Docs for v1 and v2, a site in en and de, or a stable and a next channel usually share one structure: the same page exists in several editions. Building a second site for each edition duplicates the navigation, the search and every link. dimensions() treats a version or a locale as a coordinate on the same outline instead. It wraps N outlines (one per coordinate) into a single Outline that site() consumes, and parses the coordinate off the front of the slug.
If your versions are genuinely different content, they belong in different collections, and you don’t need this page; dimensions are for one structure read at several coordinates.
An axis over collections
You declare the axes, plus a resolve function that returns the outline for a coordinate. Versions are ordinary collections, so resolve returns whichever collection holds each one:
import { site, dimensions } from '@ogygia/content';
import { v1, v2 } from './collections.server';
export const docs = site({
base: '/docs',
outline: dimensions({
axes: { version: { values: ['v2', 'v1'], default: 'v2', label: 'Version' } },
resolve: ({ version }) => (version === 'v1' ? v1 : v2)
})
});
// /docs/routing → v2 (the default serves bare)
// /docs/v1/routing → v1export const docs = site({
base: '/docs',
outline: dimensions({
axes: {
version: { values: ['v2', 'v1'], default: 'v2', label: 'Version' },
locale: { values: ['en', 'de'], default: 'en', label: 'Language', fallback: true }
},
resolve: ({ version, locale }) => corpora[version][locale]
})
});
// /docs/routing → v2 × en
// /docs/de/routing → v2 × de
// /docs/v1/de/routing → v1 × de — the axes composeThe default coordinate is served at the bare URL and the others are prefixed. resolve is called once per distinct coordinate and the result is memoized, so a version’s outline is built lazily on its first visit.
Fallback, not 404
Missing translations are the most common failure on versioned docs sites. With dimensions, an untranslated page renders the default coordinate’s content at its own URL instead of answering 404. Because every address is guaranteed to resolve, the switcher can always link the same slug across coordinates.
docs.page() reports what happened in its fallback field, so the page can show a “not yet translated” note:
<script>
const view = await docs.page(page.params.slug);
// view.coordinate → { locale: 'de' }; view.fallback → { axis: 'locale', from: 'de', to: 'en' }
// fallback is null when the page is native to its coordinate
</script>
{#if view.fallback}
<aside>This page isn't translated to {view.fallback.from} yet — showing the {view.fallback.to} original.</aside>
{/if}Fallback is opt-in per axis (fallback: true on the axis). A locale usually wants it, but a version usually does not, because a page missing from v1 most often means it did not exist yet, and that should be a 404.
The switcher
docs.switcher(slug) returns the version/locale picker for the coordinate in slug, with its hrefs already filled in. It is a serializable value (one SwitcherAxis per axis) that the <Switcher> component renders as a dropdown. Switching keeps you on the same page, landing on its equivalent in the chosen coordinate (or in the default, by the fallback rule).
You will rarely call it directly, because docs.meta(slug) bundles { nav, switcher, data } in one call and <DocsShell {meta}> renders the switcher whenever it is non-null.
Nav and search across the matrix
nav(slug): pass the slug so that the tree reflects that coordinate; the remote passes it through for you. A bare coordinate prefix such as'v1/'works too.docs.entries(): includes the union of addresses across the matrix, so a page that exists in only one coordinate still prerenders there.- search: indexes only the canonical addresses of the default coordinate, so fallback pages under other coordinates don’t show up as duplicate hits.
Coordinate as data
The coordinate is derived from the slug and passed along as data, never kept in module-level state, which makes it safe under concurrent SSR requests. You never need to reach for it yourself: nav, page, switcher and entries all take the slug and derive it.
Do / don’t
- Do model versions and locales as axes on one outline, which keeps one structure instead of N parallel sites.
- Do rely on the fallback for locales, so that a missing translation degrades to the default rather than breaking the URL.
- Don’t keep the coordinate in module state. Pass the slug, from which the coordinate is derived purely and safely per request.
- Don’t index every coordinate for search. The default’s canonical set is the accurate result, since fallbacks are the same page.