Linting and type checking are the cheapest tests you will ever run, and they only pay off when they fail the pipeline. The 2026 toolchain around Nuxt is ESLint 10 with flat config generated by @nuxt/eslint, stylistic rules instead of a separate formatter, and vue-tsc type checking, with Rust-based linters for speed where the ruleset allows.
@nuxt/eslint generates a project-aware flat config (.nuxt/eslint.config.mjs) that knows your aliases, auto-imports and component directories; you extend it in eslint.config.mjs with withNuxt(...). It includes eslint-plugin-vue and TypeScript rules; the stylistic option enables @stylistic/eslint-plugin so formatting (quotes, semicolons, indentation, trailing commas) is a lint rule with --fix, removing Prettier and the two-tool conflicts.eslintrc removed in v9 already), ESM config, eslint.config.mjs; plugins ship as objects, extends is replaced by array composition; --cache and the new --concurrency flag speed up large repos.vue/no-ref-as-operand, vue/no-unused-refs, vue/require-explicit-emits, vue/no-mutating-props, vue/prefer-import-from-vue, vue/component-name-in-template-casing, vue/block-order; TypeScript: @typescript-eslint/no-floating-promises and switch-exhaustiveness-check (type-aware, need parserOptions.projectService), consistent-type-imports; Nuxt-specific: nuxt/prefer-import-meta, nuxt/nuxt-config-keys-order; import hygiene with import-x (no-cycle where affordable).nuxt build by default (typescript.typeCheck: true enables it in dev/build through vite-plugin-checker); the CI gate is nuxt typecheck, which runs vue-tsc --noEmit over the generated project references (app, server, shared, node). TypeScript 7 (Go native, GA July 2026) is not yet usable by vue-tsc, which depends on the TS 6 API; the TS 7.1 plugin API is expected to unlock it. Projects therefore run TS 6 for vue-tsc today; tsgo can type check plain .ts (server, shared) in parallel for speed.tsgolint; the pragmatic setup runs Oxlint first (eslint-plugin-oxlint disables overlapping ESLint rules) and ESLint for stylistic and type-aware rules that Oxlint lacks. Biome is a formatter plus linter with Vue support that improved through 2.x; it fits teams wanting one tool, at the cost of a smaller rule set and no ESLint plugin ecosystem. Nuxt's official path is still @nuxt/eslint.editor.codeActionsOnSave running source.fixAll.eslint, Vue's official extension (Volar) for template type checking, and a shared .vscode/settings.json and extensions.json in the repo so the whole team gets the same feedback.simple-git-hooks or lefthook with lint-staged runs eslint --fix on staged files for fast feedback; the CI gate runs the full lint and nuxt typecheck regardless, because hooks can be skipped. Warnings are failures in CI (--max-warnings 0), and // eslint-disable needs a reason comment and, ideally, a rule counting them..ts/.vue in app/ and server/, use projectService, and cache; if it is still too slow, move them to a separate CI job.pnpm validate), Markdown lint for docs, nuxt-config-keys-order and knip for unused files and exports.A complete eslint.config.mjs for a Nuxt 4 project with stylistic rules and Oxlint in front:
export default defineNuxtConfig({
modules: ['@nuxt/eslint'],
eslint: { config: { stylistic: { indent: 2, quotes: 'single', semi: false, commaDangle: 'always-multiline' } } },
typescript: { typeCheck: false }, // CI runs nuxt typecheck; keep dev fast
})
import withNuxt from './.nuxt/eslint.config.mjs'
import oxlint from 'eslint-plugin-oxlint'
export default withNuxt(
{
files: ['**/*.ts', '**/*.vue'],
languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname } },
rules: {
'vue/no-ref-as-operand': 'error',
'vue/require-explicit-emits': 'error',
'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }],
'vue/block-order': ['error', { order: ['script', 'template', 'style'] }],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'@typescript-eslint/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports' }],
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
},
{ files: ['scripts/**', 'modules/**'], rules: { 'no-console': 'off' } },
...oxlint.configs['flat/recommended'], // turn off rules Oxlint already enforces
)
{
"scripts": {
"lint": "oxlint && eslint . --cache --max-warnings 0",
"lint:fix": "oxlint --fix && eslint . --cache --fix",
"typecheck": "nuxt typecheck"
}
}
Fast local feedback, full gate in CI:
{
"simple-git-hooks": { "pre-commit": "pnpm lint-staged" },
"lint-staged": { "*.{ts,vue,js,mjs}": "eslint --cache --fix", "*.{yml,yaml}": "pnpm validate" }
}
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with: { node-version-file: .nvmrc, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
Type checking server/ and shared/ with the native compiler while vue-tsc handles .vue:
{
"scripts": {
"typecheck": "nuxt typecheck",
"typecheck:server": "tsgo -p .nuxt/tsconfig.server.json --noEmit"
}
}
Different ESLint versions between the extension and the project, a stale .nuxt/eslint.config.mjs after adding a module, or type-aware rules that only run with projectService in CI. Run pnpm lint from the terminal before assuming the editor is right, and keep nuxt prepare in postinstall so the generated config exists on a fresh clone.
Two formatters with slightly different opinions produce endless fix loops in the editor. Pick @nuxt/eslint stylistic (or Biome) and remove Prettier and its ESLint plugin entirely.
The current @nuxt/eslint options, ESLint 10 flags such as --concurrency, Oxlint's Vue plugin status and eslint-plugin-oxlint compatibility, and whether vue-tsc has shipped TypeScript 7 support.
projectService and no-floating-promises; count the findings in a project and fix the real ones (unawaited navigateTo, refresh() calls).nuxt typecheck in CI on a project that only had editor type checking; fix what it finds in server/ and shared/."With @nuxt/eslint, which generates a project-aware ESLint 10 flat config and, with the stylistic option, replaces Prettier so formatting is a fixable lint rule with no tool conflicts. I add the Vue rules that catch real bugs, such as refs used as operands and mutating props, and type-aware TypeScript rules like no-floating-promises through projectService. For speed I run Oxlint first and let ESLint handle stylistic and type-aware rules. Type checking is nuxt typecheck, which runs vue-tsc over Nuxt's project references; vue-tsc is still on TypeScript 6 until it supports the TS 7 API, so I keep that pinned and can type check plain server and shared code with the native compiler in parallel. Hooks give fast local feedback, but the CI gate runs everything with warnings as errors, because hooks can be skipped."
Nothing yet. Notes filed here show up on this page and the section overview.
Vite and the build
How Nuxt builds with Vite 8 and Rolldown — dev server versus production, the client and server builds, Nitro's second bundling step, chunking and manual chunks, analysing bundles, environment variables at build versus runtime, source maps, build performance and the migration from Vite 7.
Package management and monorepos
pnpm as the default package manager — the content-addressable store, strict node_modules, lockfiles and frozen installs, catalogs, overrides and patches, workspaces and Turborepo task graphs, consuming Nuxt layers and internal packages, publishing with changesets and provenance, Node version management with Corepack, and supply-chain hygiene.