extends is the single config option that turns an app into a consumer of layers. Nuxt hands the list to c12, which resolves each entry, downloads remote ones with giget, loads every layer's nuxt.config, and merges the results. Interviewers ask about the sources because each one has a different operational story: a local path hot-reloads, an npm package is versioned, a git reference needs authentication and a cache.
export default defineNuxtConfig({
extends: [
'../base', // local path, relative to this config file
'@team/nuxt-layer-auth', // npm package (resolved from node_modules)
'github:team/nuxt-layer-analytics#v2.1.0', // git via giget
],
})
rootDir. Anything under ~~/layers/ does not even need to be listed: since Nuxt 3.12 every subdirectory of layers/ that has a nuxt.config.ts is registered automatically, and since 3.16 it gets a #layers/<folder> alias.main must point at its nuxt.config.ts, and the layer's runtime dependencies must be in its dependencies, because consumers install them transitively.github:owner/repo, github:owner/repo/sub/dir, github:owner/repo#branch-or-tag, plus gitlab: and bitbucket:. No ref means main. Options go in a tuple:export default defineNuxtConfig({
extends: [
['github:team/nuxt-layer-private#v1.4.0', {
install: true, // run the package manager for the layer's dependencies
auth: process.env.GITHUB_TOKEN, // or set GIGET_AUTH in the environment
meta: { name: 'private' }, // gives the layer a name -> #layers/private
}],
],
})
GIGET_AUTH=<token> works without touching the config; GIGET_GITHUB_URL / GIGET_GITLAB_URL point giget at self-hosted instances.node_modules are not yours: "if you are extending a remote source as a layer, you will not be able to access its dependencies outside of Nuxt". That is why install: true exists and why teams eventually move to npm for anything with dependencies.extends: '../base'), and the array order matters: the first entry has the highest priority among the extended layers (details on the next page).Which source fits which stage of a team's layer story:
| Stage | Source | Why | What bites |
|---|---|---|---|
| Prototype in one repo | layers/base (auto) or '../base' | zero setup, hot reload, one PR changes layer and app | nothing enforces a version boundary |
| Monorepo, several apps | pnpm workspace package @team/nuxt-layer-base (workspace:*) | still hot-reloads, versioned in one lockfile, publishable later | apps must upgrade together unless you publish |
| Many repos, private registry | npm @team/nuxt-layer-base@^2 | real semver, changelog, Renovate PRs | needs a registry and a release pipeline |
| Many repos, no registry | github:team/nuxt-layer-base#v2.3.0 | tags give versions for free | tokens in CI, install: true, cache invalidation |
c12 caches a git layer by its source string. If someone force-moves the v2.3.0 tag, consumers keep the old download until the cache is cleared. Treat tags as immutable, or extend by commit hash for anything that must be reproducible.
Where c12 stores downloaded layers (it uses a directory under node_modules/.c12/) and how to invalidate it. Confirm the exact path in the c12 README before claiming it.
layers/base/nuxt.config.ts and confirm it is picked up with no extends entry (log _layers from a local module).../base-outside and add extends: ['../base-outside']; confirm it is still one layer, not two.v0.1.0, and extend it as github:you/base-outside#v0.1.0 with GIGET_AUTH set. Delete the token and watch the error you get, so you recognise it later."Three sources: a local path, an npm package, or a git reference fetched by giget, all listed in extends; folders under layers/ are picked up automatically. For a team I would start in a pnpm workspace so layer and apps move together with hot reload, then publish to the internal registry once more than a couple of repos consume it, because npm gives us semver, a changelog and Renovate. Git tags work without a registry, but you pay with tokens in CI, install: true for dependencies, and a cache you have to understand."
Anatomy of a layer
A layer is a Nuxt app directory that another app merges in. What goes where, how Nuxt resolves its srcDir, and how to see the resolved layer list.
Priority and merging
Which layer wins when two define the same file or option, how c12 and defu merge configs, and how to prove the order instead of memorising it.