In Vue 2, Vetur and older rule sets trained developers to ignore a class of “editor-only” false positives. After Vue 3, we treat Volar + eslint-plugin-vue as the source of truth, with Prettier as the last word on formatting. If your pipeline still runs a different TypeScript or Vue compiler version than local dev, you are debugging ghosts.
1. One Node version, one lockfile
Pin Node and pnpm/yarn in CI and developer machines before chasing rule diffs. Tooling upgrades amplify when peer dependency trees pull two copies of a parser.
2. Update eslint-plugin-vue and the parser together
vue-eslint-parser must match the Vue major you compile. If you are mid- Vite migration, make sure the same SFC parser version runs in vite and eslint jobs.
3. Prettier: single file, single opinion
Disable stylistic rules that Prettier already owns. Otherwise you get red squiggles in the editor and green CI—or the inverse. Your checklist should include “format the repo once” and lock in a ratchet (lint-staged + CI) so the Vue 3 branch does not drown in noise.
4. The flat-config migration, file by file
ESLint 9 deprecated the .eslintrc.* family in favor of eslint.config.js. For a Vue 3 codebase the migration is a real benefit: per-glob configs replace the brittle overrides stack, and parser/plugin wiring becomes explicit instead of implicit.
A minimal Vue 3 + TypeScript flat config that we ship to clients looks like:
// eslint.config.js
import js from "@eslint/js";
import vue from "eslint-plugin-vue";
import vueParser from "vue-eslint-parser";
import tsParser from "@typescript-eslint/parser";
import ts from "@typescript-eslint/eslint-plugin";
import prettier from "eslint-config-prettier";
export default [
js.configs.recommended,
...vue.configs["flat/recommended"],
{
files: ["**/*.vue"],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
extraFileExtensions: [".vue"],
},
},
plugins: { "@typescript-eslint": ts },
rules: {
"vue/multi-word-component-names": "off",
"vue/no-v-html": "warn",
},
},
prettier, // must be last
];Two things to notice: vue-eslint-parser wraps @typescript-eslint/parser so <script lang="ts"> blocks are linted with type-aware rules, and eslint-config-prettier is the very last entry so formatting rules from earlier presets are turned off.
5. The Volar / Vetur changeover
Vetur was the Vue 2 default. Volar (now Vue Language Tools) is the Vue 3 default and the only path that supports <script setup> and full TS type-checking inside templates. Mid-migration, we have seen teams run both at once "just in case." That is the source of half the false positives in their inbox.
Switchover checklist
- Disable Vetur in every developer's VS Code, not just the lead's machine. Add a workspace
.vscode/extensions.jsonwithunwantedRecommendationsfor Vetur. - Enable Volar's "Takeover Mode" so it handles all
.ts,.js, and.vuefiles. Disable the built-in TS extension for the workspace. - Pin the Vue Language Tools version in
.vscode/extensions.jsonrecommendations so the team is on the same minor. - Verify
vue-tsc --noEmitruns in CI to catch what the editor catches locally.
6. SFC compiler diffs that bite
The Vue 3 SFC compiler is stricter than Vue 2's. A few patterns that compile silently in Vue 2 and fail (or warn) in Vue 3:
- Multiple root nodes. Allowed in Vue 3, but breaks
$attrsfall-through if you don't bind them explicitly. - Filters. Removed.
{{ value | currency }}compiles to garbage.eslint-plugin-vue'svue/no-deprecated-filterrule catches them. v-modelon components. Default prop changed fromvaluetomodelValue; default event frominputtoupdate:modelValue. Migrating these by hand is error-prone — let lint flag them.- Functional components. The Vue 2
functional: trueSFC syntax is gone. Convert to plain function components or full SFCs. - Async setup without Suspense. A
setup()that returns a Promise without a<Suspense>boundary will render nothing. Add the rulevue/no-async-in-computed-propertiesand audit.
Pair these with the migration blockers list for a fuller view of what slows teams down.
7. Order of operations we recommend
| Step | Action | Why |
|---|---|---|
| 1 | Pin Node, package manager, lockfile | Eliminates "works on my machine" tooling drift |
| 2 | Run Prettier once on the whole repo | Removes formatting noise from migration PRs |
| 3 | Migrate to ESLint flat config | Required before some Vue 3 rules ship |
| 4 | Bump eslint-plugin-vue + parser together | Avoids parser/plugin version mismatch |
| 5 | Switch from Vetur to Volar; Takeover mode | Editor + CI agree on what is an error |
| 6 | Add vue-tsc to CI | Type-checks templates, not just script blocks |
| 7 | Lock in lint-staged + pre-commit | Keeps the Vue 3 branch from drifting |
8. Common pitfalls
- Two parsers, one repo. A leftover Vue 2
vue-eslint-parserand a Vue 3 one in different workspaces of a monorepo will produce contradictory errors. Hoist the parser version to the root. - Prettier fighting ESLint stylistic rules. If
eslint-config-prettieris missing or not last, you get the classic "format on save undoes lint --fix" loop. - Editor running a different TypeScript than CI. Pin
typescriptin the workspace and tell VS Code to use it ("Use Workspace Version"). - Disabling rules instead of fixing. A migration is a great moment to delete legacy
// eslint-disable-next-linecomments and fix the underlying code. Track the count as a metric. - Treating formatting as a code review topic. If reviewers are still arguing about commas, your tooling is not finished.
9. What good looks like
- One command —
pnpm lint— runs ESLint, Prettier check, andvue-tsc, and matches what CI runs. - A new developer's first commit passes pre-commit hooks without manual intervention.
- Zero
eslint-disabledirectives added during the last release cycle. - The number of "lint failed in CI but green locally" tickets is zero.
- Volar takeover mode is on; nobody on the team has Vetur installed.
FAQ
Should we migrate ESLint config before or after the Vue 3 codebase migration?
Before. Lint rules that flag Vue 2 deprecations save days of manual review during the actual conversion. Treat the tooling pass as the first chapter of the migration roadmap.
Can we keep Vetur for the Vue 2 surfaces during a strangler migration?
Technically yes, but the cognitive cost of two language servers giving different answers is large. Volar can handle Vue 2 SFCs adequately for the migration window.
Is vue-tsc slow in CI?
It is slower than plain tsc. We run it on PRs to changed packages, not the entire monorepo, and run a full check nightly.
Do we need ESLint at all if Volar catches type errors?
Yes. Volar catches type and template-binding errors. ESLint catches code-style and Vue-idiom issues that Volar does not. They are complementary; both interact with how a Vite migration reshapes the build pipeline.
Tooling stuck on Vue 2-shaped assumptions?
We help teams get one green path from editor to pipeline.
Book a reviewConclusion
The Vue 3 compiler is strict in helpful ways. Lean into updated lint and format tooling early so human reviewers spend time on behavior, not on arguing about trailing commas in SFCs.
