Tooling, CI/CD & DX

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.

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.

Know

  • @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.
  • ESLint 10 (2026) is flat-config only (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.
  • Rules worth enabling for Vue and Nuxt: 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).
  • Type checking: Nuxt does not type check during 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.
  • Fast linters: Oxlint (Rust, from the Oxc project) covers hundreds of ESLint-compatible correctness rules at 50 to 100 times the speed and has a Vue plugin and type-aware rules via 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 integration: the ESLint extension with flat config enabled, 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.
  • Pre-commit versus CI: 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.
  • Type-aware linting cost: rules that need the type checker multiply lint time; scope them to .ts/.vue in app/ and server/, use projectService, and cache; if it is still too slow, move them to a separate CI job.
  • Content and config linting: YAML schema validation for content (this app's pnpm validate), Markdown lint for docs, nuxt-config-keys-order and knip for unused files and exports.

How it works

A complete eslint.config.mjs for a Nuxt 4 project with stylistic rules and Oxlint in front:

nuxt.config.ts
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
})
eslint.config.mjs
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
)
package.json (scripts)
{
  "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:

package.json (lint-staged + hooks)
{
  "simple-git-hooks": { "pre-commit": "pnpm lint-staged" },
  "lint-staged": { "*.{ts,vue,js,mjs}": "eslint --cache --fix", "*.{yml,yaml}": "pnpm validate" }
}
.github/workflows/ci.yml (excerpt)
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:

package.json (scripts, optional speed-up)
{
  "scripts": {
    "typecheck": "nuxt typecheck",
    "typecheck:server": "tsgo -p .nuxt/tsconfig.server.json --noEmit"
  }
}
Gotcha· The editor says it is fine, CI fails

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.

Gotcha· Prettier and stylistic both on

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.

Verify before the interview:

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.

docs ↗

Exercise

Exercise
  • Enable projectService and no-floating-promises; count the findings in a project and fix the real ones (unawaited navigateTo, refresh() calls).
  • Add Oxlint in front of ESLint and measure lint time before and after on your largest project.
  • Run nuxt typecheck in CI on a project that only had editor type checking; fix what it finds in server/ and shared/.

Be able to say

Be able to say· How do you set up linting and type checking for a Nuxt team?

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

Your notes

Open in Notes

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