Nuxt hides most of the build, which is why a senior needs to know what it does: two Vite builds, one Nitro bundling step, and a set of defaults that are right until they are not. The under the hood section covers the pipeline in detail; this page focuses on the tooling knobs you turn in practice.
build.rollupOptions to build.rolldownOptions, and Nuxt 4.5 made it the default builder (Nuxt 4.4 was still on Vite 7.3).nuxt dev --host, --https, --tunnel, --qr for device testing; --profile for startup profiling..output/public/_nuxt/* with hashed filenames and a manifest) and a server build (the SSR bundle in .nuxt/dist/server), then Nitro bundles the server with Rollup (Nitro 2) into .output/server for the chosen preset, including the renderer and your server/ routes.vite.build.rolldownOptions.output.codeSplitting (Rolldown's replacement for manualChunks, whose object form Vite 8 removed) and keep entry hashes stable with experimental.entryImportMap. nuxt analyze runs rollup-plugin-visualizer for client and server and prints the largest modules.import.meta.env or process.env in nuxt.config.ts is baked in; anything the running server should read goes through runtimeConfig and the NUXT_* overrides. .env is loaded by nuxt dev/nuxt build (and nuxt preview, with --dotenv), never by the production server, which gets real environment variables.sourcemap: { server: true, client: 'hidden' } gives stack traces in production and uploads to error tracking without exposing maps publicly; Sentry and similar plugins upload them at build.nuxt build time is dominated by the two Vite builds and Nitro; wins come from fewer heavy dependencies, optimizeDeps for dev, avoiding giant barrel imports (tree-shaking cannot save you from import { x } from 'huge-lib' when the lib has side effects), vite.build.target matching your browsers, disabling nuxt analyze-only plugins in CI, and caching node_modules and the Nitro output between pipeline steps.manualChunks object form, and CSS minification, which Vite 8 hands to Lightning CSS by default (build.cssMinify: 'esbuild' switches back). Nuxt manages the Vue plugin and Vite version; do not add @vitejs/plugin-vue yourself.builder: 'webpack' and 'rspack' (via @nuxt/rspack-builder) for legacy plugin ecosystems; the default and recommendation is Vite.Tuning the client build for stable caching and a smaller entry:
export default defineNuxtConfig({
sourcemap: { server: true, client: 'hidden' },
vite: {
build: {
target: 'baseline-widely-available', // Vite 7+ default; explicit here
rolldownOptions: {
output: {
// Rolldown: group vendor code so app changes do not invalidate it
codeSplitting: {
groups: [
{ name: 'vendor-vue', test: /node_modules\/(vue|@vue|vue-router|pinia)\// },
{ name: 'vendor-charts', test: /node_modules\/(echarts|d3)/ },
],
},
},
},
},
},
experimental: { entryImportMap: true }, // keeps entry chunk hash stable across route changes
})
Finding what to cut:
nuxt analyze # opens client and server visualisations, prints top modules
NUXT_ANALYZE=1 pnpm build # equivalent through env in some CI setups
Build-time versus runtime configuration, side by side:
export default defineNuxtConfig({
// baked at build: changing needs a rebuild
app: { head: { title: process.env.SITE_TITLE ?? 'Shop' } },
// read at runtime: NUXT_API_BASE / NUXT_PUBLIC_SITE_URL override per environment
runtimeConfig: { apiBase: 'http://localhost:4000', public: { siteUrl: 'http://localhost:3000' } },
})
The output you deploy:
.output/
├── public/ static assets, /_nuxt/*.js|css with content hashes (immutable caching), prerendered HTML
├── server/
│ ├── index.mjs Nitro entry for the preset (node-server here)
│ ├── chunks/ renderer, routes, your server/ code
│ └── node_modules/ externals Nitro decided not to inline
└── nitro.json preset metadata
runtimeConfig.public.* and anything referenced from client code, including via import.meta.env.VITE_*, ends up in the JavaScript that ships to browsers. Only runtimeConfig non-public keys stay on the server; grep the .output/public/_nuxt folder for a suspected value before you ship.
Rolldown replaced Rollup; plugins relying on Rollup-only hooks or on this.getModuleInfo shapes may need updating. Check the plugin's Vite 8 support note before upgrading Nuxt minor versions that move the Vite major.
Vite 8's current Node requirement, the build.minify default, the codeSplitting option shape under build.rolldownOptions.output, and the Nuxt version that made Vite 8 the default (4.5 at the time of writing).
nuxt analyze on a project and remove or lazy-load the two largest client modules; measure the entry size before and after.sourcemap.client: 'hidden' and confirm that no .map URL is referenced from the built JavaScript while error tracking still symbolicates.import.meta.env to runtimeConfig and prove you can change it with an environment variable without rebuilding."Nuxt generates its virtual app in .nuxt, then runs two Vite builds: the client build producing hashed assets in .output/public/_nuxt with a manifest, and the server build producing the SSR bundle. Nitro then bundles the server entry, the renderer and my server/ routes for the chosen preset into .output/server, inlining or externalising dependencies as configured. Since Vite 8 the bundler is Rolldown with Oxc transforms, which made builds faster. Build-time values are baked in; anything that must change per environment goes through runtimeConfig and is read from NUXT_* variables when the server starts. I check the result with nuxt analyze and ship hidden source maps to error tracking."
Nothing yet. Notes filed here show up on this page and the section overview.
Tooling & delivery
The build and delivery toolchain a senior Nuxt engineer owns — Vite and Rolldown in Nuxt, linting, formatting and type checking as CI gates, package management and monorepos, CI/CD pipelines, and the developer-experience and debugging tools that make a team fast.
Linting, formatting and types in CI
ESLint 10 flat config through @nuxt/eslint with stylistic rules instead of Prettier, Vue and TypeScript rule sets worth enabling, type checking with vue-tsc via nuxt typecheck and the TypeScript 7 situation, Oxlint and Biome as fast complements, editor integration, pre-commit hooks versus CI gates, and treating warnings as failures.