Outside app/ the project root holds the server, the shared code, the layers and modules, and a handful of dotfiles that most developers never open. The docs give each one a page, and the small ones (.nuxtrc, .nuxtignore, .env) are exactly the kind of thing an interviewer uses to see whether you read beyond the tutorials.
content/ — used by the Nuxt Content module for Markdown, YAML, JSON and CSV files that become queryable data; not scanned by Nuxt core.layers/ — auto-registered layers (Nuxt 3.12+), each with an automatic alias #layers/<folder>; priority: the project overrides layers, and auto-scanned layers override each other alphabetically with later letters winning, with extends entries below them. Layers can contain everything an app can (app/, server/, nuxt.config.ts, app.config.ts).modules/ — local modules auto-registered: modules/my-module.ts or modules/my-module/index.ts, run before modules from nuxt.config (local modules first).node_modules/ — installed by the package manager; Nuxt resolves modules and Vite pre-bundles dependencies from here.public/ — served at the site root, unprocessed, as /.server/ — Nitro: api/ (routes under /api/*), routes/ (routes without the prefix), middleware/ (runs on every request, must not return a response), plugins/ (defineNitroPlugin), utils/ (auto-imported in server code). Server alias #server (4.3+) for imports inside the server tree. Recipes: route parameters [id].ts, method suffixes .get.ts/.post.ts, catch-all [...].ts, body with readBody/readValidatedBody, query with getQuery/getValidatedQuery, errors with createError, status codes with setResponseStatus, runtime config with useRuntimeConfig(event). Server types via server/tsconfig.json referencing .nuxt/tsconfig.server.json.shared/ — (3.14+) code usable by both the Vue app and Nitro; shared/utils and shared/types are auto-imported, the rest via #shared; it must not import Vue or Nitro code, because the two bundles are separate; import type across the boundary is fine.test/ — the recommended place for tests, not scanned; a common layout is test/unit/ (plain Node), test/nuxt/ (Nuxt runtime environment) and test/e2e/ (against a running app), organised as Vitest projects..env — loaded by the CLI (via c12) in development, build and generate; a different file with --dotenv .env.local; editing it in dev restarts the server. In production you set the environment yourself; the file is not read by the built server. Add it to .gitignore..gitignore — at least .output, .data, .nuxt, .nitro, .cache, dist, node_modules, logs, .env, .DS_Store..nuxt/ — generated in development: types (imports.d.ts, components.d.ts, tsconfig.*.json), templates, the dev build. Regenerate with nuxt prepare; never edit or commit..nuxtignore — .gitignore-style patterns of root files Nuxt should ignore at build (app/pages/*-ignore.vue, app/layouts/foo.vue); also ignore, ignoreOptions and ignorePrefix in nuxt.config..nuxtrc — flat configuration in unjs/rc9 syntax (ssr=false, modules[]=@nuxt/image); nuxt.config wins over it; Nuxt writes a setups section there to track module install/upgrade state (used for module onInstall/onUpgrade hooks)..output/ — the production build: public/ (client assets, prerendered HTML) and server/ (the Nitro server, index.mjs). Deployed as a unit; never edited.nuxt.config.ts — defineNuxtConfig, with environment overrides; the file is loaded by c12 (TypeScript, .js, .mjs all work).package.json — scripts dev, build, generate, preview, postinstall: nuxt prepare; "type": "module".tsconfig.json — extends the generated configs. Nuxt 4 splits type checking with project references: .nuxt/tsconfig.app.json, .nuxt/tsconfig.server.json, .nuxt/tsconfig.shared.json, .nuxt/tsconfig.node.json, so app, server, shared and build-time code are checked with the right globals; nuxt typecheck runs vue-tsc over them.project/
├── app/ the Vue application (see the app/ page)
├── server/ api/ · routes/ · middleware/ · plugins/ · utils/ · tsconfig.json
├── shared/ utils/ and types/ auto-imported on both sides (#shared for the rest)
├── layers/ auto-registered, #layers/<name> alias
├── modules/ local modules, run before configured modules
├── public/ served as-is at /
├── content/ Nuxt Content sources
├── test/ unit/ · nuxt/ · e2e/
├── .env dev/build only; production sets real env vars
├── .nuxtrc flat rc config, overridden by nuxt.config
├── .nuxtignore build-time ignore patterns
├── .nuxt/ .output/ generated; ignored by git
├── nuxt.config.ts app.config.ts (inside app/) tsconfig.json package.json
import { z } from 'zod'
import { formatMoney } from '#shared/utils/format' // shared/ code: no Vue, no Nitro imports inside
export default defineEventHandler(async (event) => {
const { id } = await getValidatedRouterParams(event, z.object({ id: z.string() }).parse)
const product = await findProduct(id) // server/utils/products.ts, auto-imported
if (!product) throw createError({ status: 404, statusText: 'Product not found' })
setResponseStatus(event, 200)
return { ...product, price: formatMoney(product.price, 'en-GB', 'EUR') }
})
# flat syntax, rc9; nuxt.config.ts wins over this file
ssr=true
devtools.enabled=true
modules[]=@nuxt/image
A deployment that "works locally" and returns undefined secrets in production has usually relied on .env, which the CLI reads only in dev/build/generate. The built server reads the real process environment; Docker ENV, the platform's secret store or dotenv in a wrapper script is your responsibility.
The exact name and scope of the server import alias added in 4.3 and the server/tsconfig.json recommendation.
.nuxtrc with ssr=false and confirm the effect, then override it in nuxt.config.ts..nuxtignore and confirm the route disappears from the Pages panel in DevTools.shared/utils helper, use it in a page and a server route, then try importing useRoute inside it and read the error."shared/ holds code both the Vue app and the Nitro server can use: shared/utils and shared/types are auto-imported on both sides and the rest is importable through #shared. It exists because Nuxt builds two separate bundles, and neither may import the other's runtime: no Vue or Nuxt composables, no Nitro or Node-only code inside shared/; type-only imports across the boundary are fine because they are erased. Typical contents are validation schemas, formatters and pure domain logic, which is exactly what you want identical on the client and the server."
Nothing yet. Notes filed here show up on this page and the section overview.
Directory structure — app/
What the docs state for every entry under app/ — assets, components, composables, layouts, middleware, pages, plugins, utils, app.vue, app.config.ts, error.vue and router.options.ts — including scanning rules, naming and ordering.
Going further
The Going Further chapters — Experimental Features and their flags, Features and the future namespace, How Nuxt Works, Runtime Config, the Nightly Release Channel, Lifecycle Hooks, Layers, NuxtApp, Debugging, Custom Events, Server Imports, Kit and Builders.