Code editor with ESLint and Prettier configuration for a Vue 3 project
8 min readVue 3 Migration

ESLint, Prettier, and the Vue 3 Compiler: Tooling Upgrades That Feel Like Half the Migration

Teams measure migration in SFCs converted and tests green—but eslint flat config, plugin majors, and editor integration often burn the first weeks. Here is a sequencing pattern that keeps CI, VS Code, and TypeScript aligned.

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.json with unwantedRecommendations for Vetur.
  • Enable Volar's "Takeover Mode" so it handles all .ts, .js, and .vue files. Disable the built-in TS extension for the workspace.
  • Pin the Vue Language Tools version in .vscode/extensions.json recommendations so the team is on the same minor.
  • Verify vue-tsc --noEmit runs 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 $attrs fall-through if you don't bind them explicitly.
  • Filters. Removed. {{ value | currency }} compiles to garbage. eslint-plugin-vue's vue/no-deprecated-filter rule catches them.
  • v-model on components. Default prop changed from value to modelValue; default event from input to update:modelValue. Migrating these by hand is error-prone — let lint flag them.
  • Functional components. The Vue 2 functional: true SFC 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 rule vue/no-async-in-computed-properties and audit.

Pair these with the migration blockers list for a fuller view of what slows teams down.

7. Order of operations we recommend

StepActionWhy
1Pin Node, package manager, lockfileEliminates "works on my machine" tooling drift
2Run Prettier once on the whole repoRemoves formatting noise from migration PRs
3Migrate to ESLint flat configRequired before some Vue 3 rules ship
4Bump eslint-plugin-vue + parser togetherAvoids parser/plugin version mismatch
5Switch from Vetur to Volar; Takeover modeEditor + CI agree on what is an error
6Add vue-tsc to CIType-checks templates, not just script blocks
7Lock in lint-staged + pre-commitKeeps the Vue 3 branch from drifting

8. Common pitfalls

  • Two parsers, one repo. A leftover Vue 2 vue-eslint-parser and 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-prettier is missing or not last, you get the classic "format on save undoes lint --fix" loop.
  • Editor running a different TypeScript than CI. Pin typescript in 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-line comments 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, and vue-tsc, and matches what CI runs.
  • A new developer's first commit passes pre-commit hooks without manual intervention.
  • Zero eslint-disable directives 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 review

Conclusion

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.

Related guides