Nuxt docs, chapter by chapter

Directory structure — project root

The root-level entries the docs describe — content, layers, modules, public, server, shared, test, .env, .gitignore, .nuxt, .nuxtignore, .nuxtrc, .output, nuxt.config.ts, package.json and tsconfig.json — and the rule each one carries.

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.

Know — directories

  • 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.

Know — files

  • .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.

How it works

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
server/api/products/[id].get.ts
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') }
})
.nuxtrc
# flat syntax, rc9; nuxt.config.ts wins over this file
ssr=true
devtools.enabled=true
modules[]=@nuxt/image
Gotcha· .env in production

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.

Verify before the interview:

The exact name and scope of the server import alias added in 4.3 and the server/tsconfig.json recommendation.

docs ↗

Exercise

Exercise
  • Add a .nuxtrc with ssr=false and confirm the effect, then override it in nuxt.config.ts.
  • Ignore a page with .nuxtignore and confirm the route disappears from the Pages panel in DevTools.
  • Write a shared/utils helper, use it in a page and a server route, then try importing useRoute inside it and read the error.

Be able to say

Be able to say· What is the shared directory for, and what may it not do?

"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."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.