Nuxt docs, chapter by chapter

Getting started III — server, layers, prerendering, deployment, testing, upgrade

The last Getting Started chapters — the Nitro server, layers and their priority, crawl-based prerendering and payload extraction, deployment targets and presets, the @nuxt/test-utils testing setup, and the upgrade guide's list of Nuxt 4 changes.

The tail of Getting Started is where Nuxt stops being a Vue framework and becomes a deployable full-stack application. Testing and upgrading are the chapters candidates skip and interviewers do not.

Know — Server

  • Powered by Nitro: the server/ directory holds endpoints (server/api, server/routes), middleware (server/middleware) and plugins; Nitro provides the universal deployment presets and hybrid rendering through routeRules. Server-side $fetch to your own routes calls the handler directly without an HTTP round trip, and internal routes are typed ($fetch('/api/hello') infers the return type).

Know — Layers

  • Use cases: shared config presets, component libraries, themes, module presets. Usage: extends with a relative path, an npm package or a git URL, or drop a folder in layers/ for auto-registration. Priority: the project wins, then auto-scanned layers/ (alphabetical, later names override earlier), then extends entries (earlier entries override later ones); control it by naming (1.base, 2.theme) or by using extends for explicit ordering. The Layers section has fourteen pages on this.

Know — Prerendering

  • Crawl-based: nuxt generate (or nuxt build --prerender) starts from / and follows links; nitro.prerender.routes adds start routes, crawlLinks: false stops crawling, ignore excludes patterns. Selective: routeRules: { '/path': { prerender: true } } with nuxt build for a hybrid deployment.
  • Payload extraction (experimental.payloadExtraction, on by default): each prerendered page writes _payload.json, which client-side navigation loads instead of hitting a server.
  • Runtime configuration: prerenderRoutes('/path') inside a page adds routes discovered from data; the prerender:routes Nuxt hook (routes.add(...)) and the prerender:generate Nitro hook (route.skip = true) adjust the list programmatically.

Know — Deployment

  • Node.js server (default preset): node .output/server/index.mjs, configured with PORT/HOST or NITRO_PORT/NITRO_HOST; serve the same build under several base paths with NUXT_APP_BASE_URL; run under PM2 with an ecosystem file; NITRO_PRESET=node_cluster for cluster mode.
  • Static hosting: nuxt generate outputs .output/public; static fallback pages 200.html and 404.html serve client-rendered routes; client-side only with ssr: false for a pure SPA.
  • Hosting providers are detected automatically (zero-config presets); set explicitly with nitro.preset, NITRO_PRESET or SERVER_PRESET. CDN proxy: NUXT_APP_CDN_URL serves assets from a CDN, and proxies must forward X-Forwarded-* headers.

Know — Testing

  • Install: @nuxt/test-utils, vitest, @vue/test-utils, and happy-dom or jsdom. Unit testing: vitest.config.ts with defineVitestConfig (single project) or defineVitestProject for a projects setup; the Nuxt runtime environment is enabled per file with // @vitest-environment nuxt, by naming files *.nuxt.spec.ts, or for a whole project with environment: 'nuxt'; environmentOptions.nuxt sets rootDir, domEnvironment, overrides and built-in mocks (intersectionObserver, indexedDb).
  • Organising tests: test/unit (plain Node), test/nuxt (Nuxt environment; these directories are included in the app's TypeScript context), test/e2e; run one with npx vitest --project nuxt. Keep the Nuxt environment separate from end-to-end tests.
  • Helpers: mountSuspended, renderSuspended (Testing Library), mockNuxtImport, mockComponent, registerEndpoint. Browser mode via @nuxt/test-utils/browser with @vitest/browser-playwright. End-to-end: setup() from @nuxt/test-utils/e2e (options rootDir, configFile, build, server, port, host for a prebuilt server, browser, runner), then $fetch, fetch, url, createPage, useTestContext; a Playwright fixture (@nuxt/test-utils/playwright) adds goto(url, { waitUntil: 'hydration' }). @nuxt/test-utils 4 requires Vitest 4 or 5.

Know — Upgrade Guide

  • Upgrading: npx nuxt upgrade --dedupe; the nightly channel for pre-releases; testing Nuxt 5 through the documented nightly or future.compatibilityVersion: 5.
  • Migrating to Nuxt 4 with npx codemod@latest nuxt/4/migration-recipe. Changes: new app/ directory structure; singleton data-fetching layer (shared keys, consistent options); corrected module loading order in layers; deduplication of route metadata; normalised component names; Unhead v2; new DOM location for the SPA loading screen; parsed error.data; more granular inline styles; page meta scanned after resolution; shared prerender data; data/error default to undefined; boolean dedupe values removed; defaults respected when clearing data and useState; pending aligned with status; key-change behaviour; shallow data reactivity; absolute paths in builder:watch; window.__NUXT__ removed; directory index scanning; template compilation changes; TypeScript configuration split into project references; removal of some experimental features and of the top-level generate config; normalised page component names.

How it works

vitest.config.ts
import { defineConfig } from 'vitest/config'
import { defineVitestProject } from '@nuxt/test-utils/config'

export default defineConfig({
  test: {
    projects: [
      { test: { name: 'unit', include: ['test/unit/**/*.spec.ts'], environment: 'node' } },
      await defineVitestProject({
        test: {
          name: 'nuxt',
          include: ['test/nuxt/**/*.spec.ts'],
          environment: 'nuxt',
          environmentOptions: { nuxt: { domEnvironment: 'happy-dom', mock: { intersectionObserver: true } } },
        },
      }),
    ],
  },
})
test/e2e/home.spec.ts
import { describe, expect, it } from 'vitest'
import { $fetch, createPage, setup } from '@nuxt/test-utils/e2e'

describe('home', async () => {
  await setup({ browser: true })

  it('renders on the server', async () => {
    expect(await $fetch('/')).toContain('<h1>')
  })

  it('has no client errors after hydration', async () => {
    const page = await createPage('/')
    const errors: string[] = []
    page.on('pageerror', e => errors.push(e.message))
    await page.waitForLoadState('networkidle')
    expect(errors).toEqual([])
  })
})
server/plugins/prerender.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('prerender:generate', (route) => {
    if (route.route?.startsWith('/preview/')) route.skip = true
  })
})
Gotcha· Mixing the Nuxt environment and e2e in one project

Files using @nuxt/test-utils/e2e must not run in the Nuxt Vitest environment; the docs insist on separate projects (or at least separate include patterns). The symptom of mixing them is a test that hangs while setup() tries to build inside an already-running Nuxt.

Verify before the interview:

The current instructions for trying Nuxt 5 (nightly channel name or compatibilityVersion) and the latest additions to the "Migrating to Nuxt 4" list.

docs ↗

Exercise

Exercise
  • Set up the two-project Vitest config above, write one unit test and one mountSuspended test, and run each project separately.
  • Run nuxt generate on a project with a dynamic route that is not linked anywhere; observe it missing, then add it with prerenderRoutes and again with the prerender:routes hook.
  • Start .output/server/index.mjs with NITRO_PORT=4000 and NUXT_APP_BASE_URL=/app/; confirm both took effect.

Be able to say

Be able to say· How is testing set up in a Nuxt 4 project?

"With @nuxt/test-utils on Vitest. Unit tests for plain logic run in a Node environment; tests that need the Nuxt runtime, components with auto-imports and composables, run in the nuxt environment, enabled per file or per Vitest project with defineVitestProject, and use helpers such as mountSuspended, mockNuxtImport and registerEndpoint. End-to-end tests use setup() from @nuxt/test-utils/e2e, which builds and starts the app, then $fetch for server HTML and createPage for a Playwright browser, and they live in their own project because the two environments must not mix. The recommended layout is test/unit, test/nuxt and test/e2e."

Your notes

Open in Notes

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