The fastest way to understand a layer is to notice that you have already written one: every Nuxt project is a layer, and the project itself is the first entry of the resolved layer list. A layer therefore has no special file format. It is a directory that Nuxt knows how to scan, and the only hard requirement is a nuxt.config.ts, even an empty one, so that Nuxt recognises the directory as a layer.
nuxt.config.ts. Without that file the folder is ignored silently, which is the first thing to check when "the layer does nothing".app/ and everything else at the root:| Path in the layer | Scanned as | Notes |
|---|---|---|
app/components/ | components | same auto-import + path-prefix naming as the app |
app/composables/, app/utils/ | auto-imports | name collisions with the app are resolved by priority |
app/pages/, app/layouts/, app/middleware/, app/plugins/ | routes, layouts, middleware, plugins | pages are additive; same route → higher layer wins |
app/app.config.ts | app config | deep-merged, project wins |
app/app.vue, app/error.vue | shell | used only if the project has none |
server/ | Nitro handlers, middleware, plugins, utils | scanned across all layers |
shared/ | shared/utils, shared/types auto-imports | Nuxt ≥ 3.14 |
public/ | static assets | merged |
modules/ | local modules | auto-registered for the project; per-layer behaviour is worth confirming (see below) |
nuxt.config.ts | config | merged with c12 + defu |
srcDir is resolved per layer. A layer written for Nuxt 4 keeps its app code in app/; Nuxt detects that folder and uses it as the layer's srcDir. A layer still on the Nuxt 3 layout (top-level components/, pages/) keeps its root as srcDir, so mixed-generation layers can coexist.nuxt.options._layers is an array of { cwd, config, configFile, meta }; index 0 is the project, followed by the layers in priority order. Kit exposes the same list resolved to directories with getLayerDirectories(), whose entries carry root, app, server, shared, public, modules, appPages, appLayouts, appMiddleware, appPlugins.$meta: { name: 'base' }; layers inside ~~/layers/ are named after their folder automatically. Named layers get an import alias, #layers/base/..., which resolves to the layer root and is the only safe way to import a layer file by path from the project (Nuxt ≥ 3.16).npm create nuxt -- --template layer my-layer gives you nuxt.config.ts, an app/ folder with a sample component and composable, a .playground/ app that extends .., and a package.json whose main is ./nuxt.config.ts. The playground is where you develop the layer; consumers never see it.npm create nuxt -- --template layer nuxt-team-base
cd nuxt-team-base && pnpm install
pnpm dev # runs `nuxt dev .playground`, the playground extends the layer
nuxt-team-base/
├─ nuxt.config.ts # the layer's own config: modules, css, runtimeConfig defaults
├─ app/
│ ├─ app.config.ts # theme tokens, defaults every app inherits
│ ├─ components/Team/ # prefixed components -> <TeamButton>
│ ├─ composables/ # useTeamAuth(), useTeamFlags()
│ └─ utils/
├─ server/api/_team/ # namespaced routes shipped to every app
├─ shared/types/ # types used by app and server
├─ .playground/ # a real Nuxt app with `extends: ['..']`
└─ package.json # "main": "./nuxt.config.ts", "type": "module"
Seeing the resolved list is a one-liner in a local module, and a useful thing to have done before an interview:
import { defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
export default defineNuxtModule({
meta: { name: 'print-layers' },
setup(_options, nuxt) {
// Index 0 is the project. Earlier entries override later ones.
for (const [i, layer] of getLayerDirectories(nuxt).entries()) {
console.log(i, layer.root, '→ app:', layer.app, 'server:', layer.server)
}
},
})
In this repository the equivalent output has two entries: the project and layers/base. Open layers/base/nuxt.config.ts and note what it does not contain: no pages, no plugins, no extends. A design-system layer can be that small.
Nothing "runs" a layer. nuxt.config.ts is loaded by c12 and merged; the directories are scanned. If you need to compute something (read the file system, branch on options), that logic belongs in a module, which the layer can list in its modules array. Keep layer configs declarative, and keep console.log out of them: they execute once per config load, in Node, and only there.
Whether a modules/ directory inside a layer is auto-registered. The modules/ docs describe the project's directory; confirm the per-layer behaviour in the layer authoring guide or by testing it in a playground.
nuxt-team-base with the layer template, read .playground/nuxt.config.ts and package.json, and write down what main points to and why.modules/print-layers.ts from above to the playground, run pnpm dev, and confirm the project appears at index 0 and the layer at index 1.nuxt.config.ts, restart, and observe that the layer silently disappears from the list. Put it back."A layer is just a Nuxt app directory: a nuxt.config.ts, an app/ folder with components, composables, pages and app.config, a server/ folder, public/, shared/. Nuxt resolves each layer's srcDir, scans the same conventions it scans in the project, and merges the configs. The project is the first item in _layers, so everything a layer ships is a default the project can override. The only hard requirement is the config file; without it the folder is not a layer at all."
Nuxt Layers
What a layer is, why a frontend team standardises on them, and the mental model that makes every other page in this section obvious.
extends and layer sources
The three places a layer can come from, the giget syntax for git layers, private repositories, and the auto-registered layers/ directory.