Configuration
One file configures the whole site — what belongs in it, and how it merges.
Everything the theme shows is data, and app/app.config.ts is where a site
states it. There is no second config file: the same duxt key carries the
theme, the documentation sources and the site's own links.
export default defineAppConfig({
duxt: {
title: 'my project',
version: 'v1.4.0',
sources: [{ path: 'docs' }]
}
});
Every key and its default is in the configuration reference; the sources entry has its own page.
How your config merges
Your duxt object is merged over the layer's defaults key by key, and an
array of yours replaces the layer's rather than being appended to it. That
matters because Nuxt's own app.config merge does the opposite: it concatenates
arrays, which would leave you unable to remove a single navbar entry the layer
ships. So the layer ships no lists in app.config at all and merges them
itself.
The practical rule: override footer.copyright and footer.legal stays
untouched; declare navigation and the layer's one entry is gone.
Two keys are read at build time
sources and locales decide the collections, the routes and the hreflang
links, and all three are settled before the first request. Changing either needs
a rebuild; everything else takes effect as soon as the config is reloaded.
Some keys are generated, not written
resolvedSources, layerVersion and layerRepository appear in the config at
runtime and belong to the build. resolvedSources is the manifest the theme
actually reads — which collection serves which URL prefix — resolved from the
sources list you wrote. Writing any of the three by hand is overwritten.
What the layer does not presume to know
links, aside.links and footer.legal ship empty. A repository, an issue
tracker, a community and an imprint each name a specific project, so a default
naming duxt's would hand your readers a "Star on GitHub" that stars somebody
else's work.
sections ships empty for the neighbouring reason: it names the top-level parts
of your documentation tree, and the layer cannot know what you called them. Set
it once your docs have sections; until then the row stays hidden and the sidebar
shows the whole tree.
sections: [
{ label: 'Guides', to: '/guides', icon: 'lucide:book-open' },
{ label: 'Reference', to: '/reference', icon: 'lucide:list' }
]
Write your own — as a plain string, or as a per-locale record if the site serves several languages:
links: [
{
icon: 'simple-icons:github',
to: 'https://github.com/you/your-project',
label: { 'en-GB': 'Repository', 'de-DE': 'Repository' }
}
]
The layer's own i18n keys translate the defaults it ships. They are internal: renaming one is not a breaking change, and i18n answers a missing key by printing the key — so the break would be silent on your site. Use a literal, a key of your own, or the record form above.
The site's origin
duxt cannot guess the domain it will be served from, and four things need it:
hreflang, canonical, the sitemap and the Open Graph images. State it once,
where Nuxt already asks:
i18n: { baseUrl: 'https://docs.example.com' }
or as NUXT_PUBLIC_I18N_BASE_URL in the deployment. The layer copies it to
site.url, which is where the SEO modules look. Left unset, each of them
degrades to relative output rather than inventing a domain — a relative
canonical still resolves, a guessed one is actively wrong. See
SEO.