Tooling, CI/CD & DX

Developer experience and debugging

Nuxt DevTools and what it reveals, debugging SSR and Nitro code with breakpoints, source maps and the Node inspector, HMR and dev-server troubleshooting, environment files and per-environment config, reproducible local environments, onboarding in ten minutes, scripts and generators, and a debugging method for the classic Nuxt failures.

Developer experience is a senior deliverable: the time from clone to running app, the time from a bug report to a breakpoint on the right line, and the confidence that local matches production. Nuxt gives more than most frameworks here, and knowing the tools well is what lets you fix a hydration mismatch in five minutes rather than an afternoon.

Know

  • Nuxt DevTools (bundled, toggled with Shift+Alt+D or the floating tab): pages and their route rules, components tree with inspection and "open in editor", composables and auto-imports with their sources, modules installed, plugins with load order and timings, hooks timeline, payload and useState contents, server routes with a request playground, runtime config and app config live editing, timeline of route changes, Vite inspect for transformed modules, a terminal for nuxt commands, and the Vue DevTools panel embedded. Also nuxt analyze for bundles and nuxt info for environment reports.
  • Debugging SSR and Nitro: nuxt dev --inspect is not a flag; instead run node --inspect node_modules/nuxt/bin/nuxt.mjs dev or a VS Code launch.json with runtimeArgs: ['--inspect'], or set NODE_OPTIONS=--inspect; debugger statements and breakpoints in server/ and in component <script setup> during SSR then work, with sourcemap: { server: true } in dev by default. The built server: node --inspect .output/server/index.mjs. nuxt dev --profile writes a startup profile for slow starts.
  • Client debugging: Vue DevTools shows component state, props, emitted events, Pinia stores and the router; $nuxt on window in dev gives the app instance and payload; the Performance panel with the Vue profiler flag (vite.define: { __VUE_PROD_DEVTOOLS__ } only for staging) for render timings; hydration mismatches log detailed diffs in Vue 3.4+ dev builds.
  • HMR and dev-server issues: stale .nuxt after changing modules or config (nuxt cleanup or delete .nuxt), duplicated Vue after pnpm linking (nuxt upgrade --dedupe), file watchers exhausted on Linux (fs.inotify.max_user_watches), Docker or WSL needing polling (vite.server.watch.usePolling), and a port in use (--port). nuxt dev --host for LAN devices, --tunnel and --qr for a phone.
  • Environment files: .env is loaded by the CLI in dev and build and mapped onto runtimeConfig through NUXT_* names; .env.example is committed, .env* ignored; nuxt dev --dotenv .env.staging picks a file; production servers get real environment variables. nuxt.config.ts can branch with $production/$development env overrides and $env for custom environments (nuxt build --envName staging).
  • Reproducible environments: pinned Node via .nvmrc and packageManager, pnpm install --frozen-lockfile, a docker-compose.yml for Postgres and Redis, seed scripts, pnpm dev starting everything through concurrently or a Nitro dev proxy for the API; dev containers or Nix for teams that need bit-for-bit parity.
  • Onboarding target: clone → pnpm install → cp .env.example .env → pnpm dev under ten minutes, with a README that is a checklist and nuxt prepare in postinstall so types and the ESLint config exist before the editor opens.
  • Scripts and generators: nuxt add component|page|composable|api|layout|middleware|plugin|layer scaffolds files in the right place; team templates through a small generator (plop or a Nitro task) for features with their tests and stories; nuxt module add for modules; nuxt info in bug reports.
  • A debugging method for Nuxt's classic failures: hydration mismatch → compare server HTML with the client render, look for Date.now(), Math.random(), locale-dependent formatting, browser-only branching, invalid HTML nesting, and browser extensions (hydration section); composable called outside context (NUXT_E1001) → an await before useX in setup or a composable called from a callback, fixed by hoisting or runWithContext; data fetched twice → mismatched useFetch keys or server: false; "window is not defined" → import-time browser access, fixed with import.meta.client or a client-only plugin; state leaking between users → module-scope state in the server, fixed with useState; stale types → nuxt prepare; works in dev, fails in build → build-time versus runtime config, externals, or a dependency that is CJS-only for the server.
  • Logging in dev: consola is Nuxt's logger (useLogger in modules); nitro.logging options, NITRO_LOG_LEVEL; server console output is proxied to the browser console in dev when features.devLogs is enabled, which is how you see console.log from server/ while looking at the page.

How it works

Breakpoints in server code and in SSR from VS Code:

.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Nuxt dev (server breakpoints)",
      "program": "${workspaceFolder}/node_modules/nuxt/bin/nuxt.mjs",
      "args": ["dev"],
      "runtimeArgs": ["--inspect"],
      "outputCapture": "std",
      "console": "integratedTerminal"
    },
    {
      "type": "chrome",
      "request": "launch",
      "name": "Nuxt client",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}"
    }
  ],
  "compounds": [{ "name": "Nuxt fullstack", "configurations": ["Nuxt dev (server breakpoints)", "Nuxt client"] }]
}

Per-environment configuration without rebuilding, plus a custom environment for the rare build-time difference:

nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    databaseUrl: '',                       // NUXT_DATABASE_URL
    public: { siteUrl: 'http://localhost:3000', flags: { newCheckout: false } }, // NUXT_PUBLIC_SITE_URL, NUXT_PUBLIC_FLAGS_NEW_CHECKOUT
  },
  $development: { devtools: { enabled: true }, features: { devLogs: true } },
  $production: { sourcemap: { server: true, client: 'hidden' } },
  $env: { staging: { app: { head: { meta: [{ name: 'robots', content: 'noindex' }] } } } }, // nuxt build --envName staging
})
cp .env.example .env
nuxt dev --dotenv .env.staging --host --qr    # staging config, LAN access, QR for a phone
NUXT_PUBLIC_FLAGS_NEW_CHECKOUT=true node .output/server/index.mjs

Local services and a one-command start:

docker-compose.yml
services:
  db:
    image: postgres:17
    environment: { POSTGRES_PASSWORD: dev, POSTGRES_DB: shop }
    ports: ['5432:5432']
    volumes: ['db:/var/lib/postgresql/data']
  redis:
    image: redis:8
    ports: ['6379:6379']
volumes: { db: {} }
package.json (scripts)
{
  "scripts": {
    "dev": "docker compose up -d && nuxt dev",
    "db:migrate": "drizzle-kit migrate",
    "db:seed": "node --env-file=.env scripts/seed.ts",
    "postinstall": "nuxt prepare"
  }
}

Seeing server logs while looking at the page, and inspecting the payload:

server/api/debug-me.get.ts
export default defineEventHandler((event) => {
  console.log('[api] request id', event.context.requestId) // shows in the browser console in dev with features.devLogs
  return { ok: true }
})
// in the browser console, dev only
$nuxt.payload            // data, state, path, prerenderedAt
$nuxt.$router.currentRoute.value
Gotcha· Editing .env and expecting the production server to notice

.env is a CLI convenience for nuxt dev, nuxt build and nuxt preview; node .output/server/index.mjs reads only real environment variables. Set them in the platform or use node --env-file=.env explicitly.

Gotcha· Breakpoints that never hit in SSR

Setting a breakpoint in a component and reloading hits only the client render, because the server is a different process. Attach the inspector to the Nuxt process (--inspect), or use the compound launch configuration so both processes are debugged.

Verify before the interview:

The current recommended way to start Nuxt with the inspector, the features.devLogs option name, and the DevTools panels list in the latest release.

docs ↗

Exercise

Exercise
  • Set a breakpoint in a server/api handler and in a component's setup during SSR and hit both with the compound launch configuration.
  • Introduce a hydration mismatch on purpose (render Date.now()) and use the Vue warning plus DevTools to locate it in under two minutes.
  • Measure clone-to-running-app time for a new colleague and fix the top two friction points.

Be able to say

Be able to say· How do you debug a bug that only happens during server-side rendering?

"I reproduce it with the server process under the inspector, either through a VS Code launch configuration that runs the Nuxt CLI with --inspect or by attaching to the built server, so breakpoints in server/ and in component setup during SSR actually hit. Nuxt DevTools shows the payload and useState contents, plugin order and the server routes, and in dev the server logs are proxied to the browser console. Then I apply the classic checklist: import-time browser access, module-scope state, composables called after an await, non-deterministic render output, mismatched fetch keys, and build-time versus runtime config. Most SSR-only bugs are one of those, and the tooling makes each of them visible in minutes."

Your notes

Open in Notes

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