Sources
The fields of a source entry, its refs, and the options that shape the URLs.
duxt.sources is a list of source entries; duxt.sourceOptions says how they
become URLs. Both are read at build time. What they generate, and why, is in
Sources.
A source entry
| Field | Type | Notes |
|---|---|---|
path | string | Folder holding the Markdown, relative to the repository root |
repo | string | owner/name or a git URL. Omitted means this repository |
refs | ref list | Refs to publish as versions. Omitted means the current checkout |
version | string | The version this source is, where no ref names one |
label | text | Shown in the switcher and used in the URL; defaults to the ref |
slug | string | This source's URL segment. Naming one claims it — see below |
status | status | Lifecycle of every version this entry publishes |
origin | { repo, ref? } | Where pages read off disk live, for links back to them |
history | boolean | Read this source's git history |
locales | locale list | Languages this source is available in — see below |
generated | section list | Artefacts published as pages beside the Markdown — see below |
Two fields are easy to confuse and expensive to get wrong:
repodownloads,originonly links. Naming your own repository inrepohas the build clone the checkout it is already standing in.historycosts a full clone. Content clones a remote repository with--depth 1, so its checkout holds one commit and every file looks as if the person who cut the tip wrote it — wrong data, not missing data. Turning this on unshallows the clone once. A source read off disk is a full checkout already and is read regardless.
slug claims a segment. A source is normally given one only once the list
names more than one repository — all or nothing, across the whole site. A source
that writes its own slug is served under it either way, and the sources that
write none stay where they are, which is what lets one thing sit at /demo
beside documentation that keeps the root.
Refs
A bare string is a branch. A tag has to say so: git keeps branches and tags
in separate namespaces, and asking for a tag under refs/heads fails the build
with Could not find refs/heads/….
refs: [
'main', // a branch
{ branch: 'next', status: 'upcoming' }, // the same, with a lifecycle
{ tag: 'v2.0.0', label: 'v2' }, // a tag
'latest' // the newest semver tag, resolved at build
]
'latest' is reserved. A branch genuinely called latest needs the { branch }
form.
Locales
Omitted, the source has one language and one collection. Listed, each entry becomes a collection of its own.
locales: [
'en-GB', // the default: docs/ itself
'de', // docs/de/
{ locale: 'fr', path: 'translations/fr' }, // somewhere else in this repo
{ locale: 'es', repo: 'acme/docs-es', path: 'docs' } // another repository
]
A string is a folder inside the source's path. The object form overrides
path, repo and ref for that language alone — which is what lets a
translation live in a repository of its own, with its own maintainers and its
own release schedule.
The default locale is the tree in path itself and takes no folder. Which
one that is comes from sourceOptions.defaultLocale, and it has to agree with
i18n.defaultLocale — the build fails when they differ, because
content.config.ts resolves the collections without access to the Nuxt config.
A ref may carry its own locales, overriding the source's exactly as status
does. That is the usual shape: the current version is translated, the ones
behind it are not.
refs: [
{ tag: 'v2.0.0' }, // inherits the source's locales
{ tag: 'v1.0.0', locales: ['en-GB'] } // original only
]
Name a folder by LANGUAGE where a region adds nothing: docs/pt/ serves both
pt-PT and pt-BR, because a page falls back through its base language. The
same rule the layer's own locale files follow.
Status
| Value | Means |
|---|---|
upcoming | Not released yet — may still change |
current | The documentation to read |
maintained | Older, still supported |
deprecated | Older, and the reader should upgrade |
eol | Dead — warned about, and out of the sitemap |
What each one costs a page is in URLs and versions.
Generated sections
generated lists artefacts that are not Markdown and the type that reads each
one into pages. What the mechanism is, and the two policies a type answers, is in
Generated sections; the fields below are the same
whichever type reads them.
| Field | Type | Notes |
|---|---|---|
type | string | The registry key of the type — changelog, openapi, your own |
path | string | The artefact, relative to the source's own root |
label | string | The navbar entry, and — slugified — the URL segment |
slug | string | Overrides the segment the label would produce |
options | object | The knobs that type offers; each type validates its own |
versions | version list | The versions of this artefact — see below |
locales | Record<string, string> | An artefact per locale, read by a per-locale type only |
navigation | placement | 'sections' (the default), 'navigation', or false |
icon | string | Falls back to the type's own |
path is a path and never a URL. For a source Content clones it is relative
to the root of that checkout, so the artefact travels with the version it belongs
to — one rule in both directions. An arbitrary location would let an artefact be
fetched from anywhere and reopen the build-time-network question sources has
already closed.
label is a plain string, not a translated text, for the same reason a
version's label is: a translated text is not a stable URL.
A section with versions of its own
A version is normally a checkout: list refs on the source and every section
it carries is published once per ref. An API is usually not kept that way —
openapi/v1.yaml sits beside openapi/v2.yaml in one repository, versioned by
file — and without this key the two are two unrelated sections with two
entries in the navbar.
generated: [
{
type: 'openapi',
label: 'API',
path: 'openapi/v2.yaml',
versions: [
{ version: 'v2', path: 'openapi/v2.yaml' },
{ version: 'v1', path: 'openapi/v1.yaml', status: 'deprecated' }
]
}
]
| Field | Notes |
|---|---|
version | Shown in the switcher and used in the URL |
path | The artefact this version reads; defaults to the declaration's |
locales | Per-locale artefacts for this version |
status | Lifecycle of this version — deprecated, eol, upcoming |
default | Served without a version segment. Defaults to the first entry |
The default is served at /api, every other version under its own segment
(/v1/api), and the switcher offers them exactly as it offers a source's refs —
with the deprecation banner, the noindex and the canonical that go with a
version that is not the current one.
Declared here rather than as two sources, and that is the design rather than a convenience: two sections are offered as versions of one another only when one declaration produced them, and a source always publishes a documentation tree — so a source per API version would publish the prose twice.
Never beside a source versioned by refs. Both want the same segment of the
URL, and the build says so instead of nesting them. A global type — a changelog
— refuses them too: it publishes one history at a version-neutral URL, so a list
of versions is a contradiction rather than a shape to resolve.
Which version is which
Three things are called a version around a reference page, and only two of them are duxt's:
- The version in the URL and the switcher — a ref, or a
versiondeclared as above. It is what the badge in the header shows: which variant you are reading. duxt.version— the site's own string, shown in that badge only where there is nothing to switch between.info.versionof an OpenAPI document — what the document says about the API it describes. It is drawn on the reference's own page, labelled API version, and never in the badge: a badge is the switcher's trigger, and a document's own claim is not somewhere you can switch to.
They often carry the same number, because a repository that tags v2.0.0 usually
writes 2.1.0 in the document it tagged. They are still different facts, and
duxt derives neither from the other.
options is opaque to everything but the type that reads it — a granularity
means something to a changelog and nothing to an API reference. A key the type
does not know fails the build rather than being ignored, because a misspelled
option is a site quietly not getting what it configured.
Source options
| Field | Default | What it does |
|---|---|---|
showRepo | off | Force a repository segment with a single repository |
showVersion | off | Force a version segment with a single version |
defaultRef | the first ref | The ref served without a version prefix |
defaultLocale | the first locale | The locale served from path itself, with no folder |
The resolved manifest
The build turns the list above into duxt.resolvedSources, and that is what the
theme reads. useDuxtCollection() exposes it; each entry carries the collection
name, the URL prefix, repo, version, isDefault, locale,
isDefaultLocale, status, the repository and ref the pages came from, and
whether its history was read.
The locale is deliberately absent from prefix: i18n puts it in front of the
path already, so every language of one source shares a prefix and differs only
by collection.