Vue 2 to Vue 3 migration: what changes and what to plan

Team planning a vue 2 vs vue 3 migration in a workspace

Quick answer: Don’t rewrite first if your Vue 2 product is stable. Inventory dependencies, use @vue/compat for plain Vue apps or a Nuxt-specific migration path for Nuxt apps, then remove warnings until the app runs on Vue 3 behavior.

Your Vue 2 app still ships. The tests pass. Customers are happy.

Then a package upgrade lands, and its next version needs Vue 3. The release stalls while the team argues whether this is a package bump, a migration-build cleanup, a Nuxt project, or a rewrite.

That’s the real risk in a Vue 2 to Vue 3 migration. Not the syntax. The risk is choosing the wrong path before you know what will break.

By Sudipto Mondal, Senior Software Engineer at VueSquad. Sudipto is a full-stack developer with 5+ years of experience across SaaS platforms, eCommerce systems, and custom software.

The practical answer: migrate, stage, or rewrite?

You need a decision before you touch package.json.

Vue 2 reached end of life on December 31, 2023. Treating it as your long-term product line is a risk, even if the app still runs.

The last Vue 2 release was Vue 2.7.16. The question is no longer, “Should we ever move?” It’s, “How do we move without freezing roadmap work?”

If your app is stable, the architecture is healthy, and key dependencies have Vue 3 paths, migrate in place. That is our default recommendation at VueSquad. If you need senior help planning or doing that work, we provide Vue 2 to Vue 3 migration help inside your existing tools and workflow.

If the app has heavy technical debt, don’t force an in-place migration. A fresh Vue 3 or Nuxt 3 codebase can be safer. That is especially true when outdated libraries, poor structure, or broad rewrites would touch most screens.

Use this decision rule first

Ask these questions before you estimate anything:

  1. Is this plain Vue or Nuxt? Nuxt has its own migration path.
  2. Do the main dependencies support Vue 3? UI libraries, router, state, plugins, charts, forms, and analytics all count.
  3. Are tests strong enough to protect user flows? If not, add tests before large changes.

A good migration starts as an inventory project, not a code-editing sprint.

When staying on Vue 2 is acceptable for a short time

Staying on Vue 2 can be acceptable while you prepare funding, staff, tests, or dependency replacements.

It is not acceptable as “we’ll think about it later” if your product depends on package upgrades, security patches, hiring, browser changes, or Nuxt module updates.

Use the delay to build a migration backlog. Don’t use it to avoid the decision.

Vue 2 vs Vue 3: the changes that affect planning

The difference between Vue 2 and Vue 3 is not only Options API versus Composition API.

Vue 3 changes the runtime, compiler, app setup, lifecycle behavior, slots, v-model, transition class names, and several APIs. Use the official Vue 2 to Vue 3 breaking changes as your checklist.

For planning, scan these areas first:

  • global app setup and plugins
  • component library wrappers
  • slots and scoped slots
  • v-model and .sync patterns
  • custom directives
  • filters and removed APIs
  • transition class names
  • class components and decorators
  • direct prop mutation
  • tests that mount components with old global mocks

Don’t turn every component into Composition API on day one

Vue 3 supports the Composition API and <script setup>, but migration should not become a rewrite of every component.

If a component is stable and boring, keep it boring. Change it only when the old pattern blocks Vue 3 behavior, TypeScript safety, testability, or future maintenance.

Teams get into trouble when they combine five projects into one release: Vue 3 migration, TypeScript adoption, UI redesign, state rewrite, and folder restructure. Pick the smallest set of changes that gets you safely onto Vue 3.

A small code pattern that affects real work

Here is a common Vue 2 parent-child update pattern:

vue
<!-- Vue 2 pattern -->
<template>
  <UserNameInput :value="name" @input="name = $event" />
</template>

<script>
export default {
  data() {
    return { name: '' }
  }
}
</script>

In Vue 3, make the model contract explicit:

vue
<!-- Vue 3 parent -->
<template>
  <UserNameInput v-model="name" />
</template>

<script setup lang="ts">
import { ref } from 'vue'

const name = ref('')
</script>
vue
<!-- UserNameInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
  />
</template>

<script setup lang="ts">
defineProps<{ modelValue: string }>()

const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()
</script>

This is not about nicer syntax. It gives tests and parent components a clear update contract.

@vue/compat and migration tools are helpers, not a converter

Tools can reduce risk. They cannot understand your product behavior.

What @vue/compat does

@vue/compat is the Vue 3 migration build. It provides configurable Vue 2-compatible behavior. It runs in Vue 2 mode by default and emits runtime warnings for changed or deprecated behavior through the @vue/compat migration build.

The setup has a few moving parts:

  • match vue and @vue/compat versions
  • replace vue-template-compiler with @vue/compiler-sfc
  • alias vue to @vue/compat
  • enable compatibility compiler options
  • use compatConfig per component as you move toward Vue 3 behavior

Warnings are not noise. They are your backlog.

Why there is no safe one-click Vue 2 to Vue 3 converter

Searchers ask for a Vue 2 to Vue 3 migration tool because they want a clean answer. The honest answer is simple: codemods help, but they do not finish the job.

The vue-codemod project presents automated changes as a starting point, and its docs say runtime behavior still needs checking through vue-codemod.

A converter cannot decide whether your date picker has a Vue 3 release. It cannot prove checkout still works. It cannot resolve a Nuxt hydration mismatch.

Exit criteria for the migration build

Don’t leave the app in compatibility mode forever.

Your exit criteria should be:

  • no compatibility warnings in normal user flows
  • known transition-class issues checked manually
  • key routes tested in production-like builds
  • Vue 3 compiler mode enabled
  • old package shims removed
  • migration notes turned into team rules

If nobody owns the warnings, compatibility mode becomes the new legacy system.

Nuxt 2 to Nuxt 3 is a separate migration plan

If your app is Nuxt, plain Vue advice is not enough.

Why Nuxt changes the migration route

Nuxt migration includes more than a Vue runtime upgrade. It moves from Vue 2 to Vue 3. It also moves from webpack 4/Babel to Vite or webpack 5/esbuild, plus Nitro’s standalone server model in the Nuxt migration overview.

Nuxt Bridge can be a useful middle step. It lets a Nuxt 2 app adopt selected Nuxt 3 features before a full migration.

But don’t assume @vue/compat solves Nuxt 3. Nuxt’s docs state that the Vue 3 migration build cannot currently be used with Nuxt 3 in the Nuxt configuration migration guide.

Bridge, direct migration, or rebuild?

Pick Bridge when the app needs staged modernization and still has Nuxt 2 constraints.

Pick direct Nuxt 3 migration when modules are healthy, pages are clear, and your team can test SSR or prerendered routes.

Pick a rebuild when the old Nuxt app is deeply tangled: unclear data fetching, stale modules, broken layouts, and content rendering that needs a new model anyway.

For Nuxt teams, we offer Nuxt migration support when the Vue 2 migration is really a Nuxt 2 migration.

Common Nuxt blockers: modules, content, and hydration

Nuxt page and layout conventions change. Nuxt 2’s layout component moves from <Nuxt> to slots, and nested pages move from <NuxtChild> to <NuxtPage> in the Nuxt pages and layouts migration guide.

vue
<!-- Nuxt 2 layout -->
<template>
  <div>
    <SiteHeader />
    <Nuxt />
  </div>
</template>
vue
<!-- Nuxt 3 layout -->
<template>
  <div>
    <SiteHeader />
    <slot />
  </div>
</template>

Nuxt modules and plugins need their own inventory. Nuxt’s module docs say Nuxt 3 plugins are not fully backward compatible with Nuxt 2 plugins, and modules may need rewriting under the Nuxt module migration guidance.

Hydration mismatches are one of the most common blockers we see in Nuxt production migrations. Hydration is the browser step where client Vue takes over server-rendered HTML. If server and client output differ, you get warnings, broken interactivity, or page-specific bugs.

Run this migration inventory before you touch package.json

The inventory prevents bad estimates.

Dependency inventory

Classify every dependency as ready, replace, rewrite, or remove. Ask for proof, not hope.

AreaWhat to checkWhy it affects the planEvidence to collect
FrameworkVue version, Nuxt versionDetermines plain Vue or Nuxt pathLocal build, migration docs
RouterRoute APIs, guards, testsRouting bugs break core flowsPassing route tests
StateVuex, Pinia, custom storesStore changes touch many screensStore list and usage map
UI libraryComponents, themes, forms, tablesOften the biggest hidden costOfficial Vue 3 support docs
Build toolVue CLI, webpack, ViteCompiler setup affects migration buildWorking production build
Test toolingUnit, component, E2EProtects behavior during editsCritical-flow test report
PluginsGlobal properties, analytics, mapsOld plugin APIs often breakPlugin owner and replacement path
SSR/SSGData fetching, hydration, prerenderNuxt bugs appear after buildPreview deployment
DirectivesCustom DOM behaviorDirective hooks may need updatesDirective usage list

Vue’s migration recommendations also tell teams to consider ecosystem moves. That includes Vue CLI to Vite, Vuex to Pinia, Vetur to Volar, and vue-tsc for TypeScript in the framework-level recommendations.

Code-pattern inventory

Search for patterns that create migration work:

  • filters
  • .sync
  • this.$children
  • direct prop mutation
  • global event buses
  • class components
  • implicit slot behavior
  • custom directives
  • components that depend on DOM timing
  • large components with unclear ownership

We’ve seen outside Vue codebases where the hard part was not Vue 3 syntax. It was large components, direct prop mutation, and unclear communication between components, composables, and stores.

Test and release inventory

Say your app has many components, many dependencies, and several critical flows.

Turn that into a backlog like this:

  • dependency decisions
  • flow tests that must pass before release
  • component groups by feature owner
  • one spike for build setup
  • one spike for UI library risk
  • one release plan with rollback

At VueSquad, migration discovery starts with one split: do you want a Vue 3 compatibility migration, or a full modernization? Those are different projects.

Choose the migration path: in-place, Bridge, direct upgrade, or fresh build

Now turn the inventory into a choice.

PathBest whenAvoid whenRollback abilityMain trade-offExit condition
Stay on Vue 2 brieflyYou need time to staff, test, or replace blockersYou treat it as the product futureHighRisk keeps growingMigration backlog approved
@vue/compatPlain Vue app, healthy structure, many componentsNuxt 3 is the target pathMediumWarnings must be ownedCompat warnings removed
Nuxt BridgeNuxt 2 app needs staged modernizationYou want to skip intermediate statesMediumAdds a temporary stateReady for Nuxt 3 migration
Direct migrationTests are strong and dependencies are readyMany dependencies are unknownLowerMore upfront changeProduction build and flows pass
Fresh Vue 3 or Nuxt 3 buildCurrent app blocks changeSize is the only concernLowerMore parity workNew app replaces old flows

Size alone is not a rewrite reason. A large, well-tested app with healthy dependencies can be safer to migrate than rebuild.

What a small Nuxt content-site migration looked like

We migrated an anonymized US music-lesson marketplace’s content-heavy Nuxt site from Nuxt 2.15 and Vue 2 to Nuxt 3.10 and Vue 3.4.

The site was roughly 40 pages. One VueSquad engineer did the migration while the client’s developer reviewed and merged work. The work took about one week.

The main blocker was not Vue component syntax. It was Nuxt Content v1 to v2 markdown rendering. Prerendering and hydration fixes came next.

When a rewrite is the honest choice

Choose a fresh build when the migration would touch most of the app anyway.

Common signs:

  • old UI library with no clean path
  • no useful tests
  • hidden business logic inside views
  • broken SSR or prerender assumptions
  • global plugins used everywhere
  • features the product no longer needs
  • layout and state structure that fights every change

A rewrite is not failure. It is failure only when you call it a migration and skip parity planning.

Failure modes to watch during the migration

Most migration failures have a clear smell. Catch them early.

SymptomLikely root causeHow to confirm itFix
App builds but pages warn in consoleCompat warnings ignoredTest real routes, not just buildTurn warnings into tickets
Layout works locally but fails after deploySSR or hydration mismatchCompare server HTML and client stateMove browser-only code behind client checks
Form library breaks everywhereVue 2 UI dependencyCheck official Vue 3 pathUpgrade, replace, or isolate
Nuxt pages render blankLayout/page convention changedCheck layout and nested page usageReplace old Nuxt components and route APIs
Tests fail before migration startsTest setup tied to old globalsRun test suite before changesUpdate test harness first
Feature teams keep reintroducing old patternsNo migration rulesReview new PRs for changed APIsAdd lint rules and review checklist

Teams on r/vuejs often describe the migration as warning cleanup plus full-flow testing, not a simple install command. That matches what we see in real projects, especially where dependencies are old.

Staffing, cost, and rollout plan

A migration costs less when the right people own the right risks.

Compare staffing options first

No reliable universal market price or timeline applies to Vue migrations. Effort depends on app size, dependency health, test coverage, Nuxt usage, and release risk.

Use a simple internal formula:

Monthly cost = people assigned × their monthly cost + delayed roadmap work

OptionBest fitInternal effort neededMain risk
In-house teamStrong Vue team with protected timeHighRoadmap work slows
Permanent hireLong-term Vue investmentMedium after onboardingHiring takes time
FreelancerBounded tasks with clear specsHigh review effortContinuity varies
Staff augmentationYou own architecture and need capacityMediumNeeds strong internal direction
Specialist Vue/Nuxt teamComplex migration or missing senior Vue depthMediumMust fit your workflow
Generalist agencyLarger web program with many stacksHigh Vue review effortVue/Nuxt edge cases missed

Where VueSquad fits

VueSquad is one option when you need senior Vue engineers who join your tools: GitHub, GitLab, Linear, Jira, and Slack. You can also hire senior Vue engineers for migration work and ongoing product development.

PlanMonthly priceMonthly hoursBest fit
Full-Time Senior Vue Engineer$2,499 per month160 hours per monthOngoing product development, SaaS platforms, large or evolving codebases, migrations and long-term maintenance
Part-Time Senior Vue Engineer$1,499 per month80 hours per monthFeature support, refactoring, performance optimization, UI modernization, and migration assistance
Dedicated Vue SquadCustom pricingCustomMultiple senior Vue engineers with engineering-lead oversight, shared standards, design system support, sprint planning, and weekly reporting

Every plan has monthly billing, flat rate, no onboarding fees, no minimum commitment, and pause or cancel anytime. Clients can start within 48 hours in most cases, meet and approve the engineer before onboarding, and work with overlapping US and EU hours.

You can review current VueSquad pricing before you compare this with your internal cost.

Rollout checkpoints

Use checkpoints that reduce release risk:

  1. Inventory approved.
  2. Target path chosen.
  3. Build setup working.
  4. Critical-flow tests passing before migration.
  5. Migration warnings assigned.
  6. Preview deployment reviewed.
  7. Production rollout has rollback steps.
  8. Old compatibility code removed.

Don’t ship because “the build is green.” Ship because the flows your users pay for are proven.

Your next step

If your app is stable, start with an inventory and plan an in-place migration. If it’s Nuxt, split that into a Nuxt migration plan before you estimate.

If you want senior Vue engineers to help choose the path, clean up warnings, and ship safely, you can plan your Vue 2 to Vue 3 migration with VueSquad.

Frequently asked questions

Is Vue 2 end of life?

Yes. Vue 2 is outside the official support line, as covered near the top. You can keep a Vue 2 app running, but treat that as a short-term state while you plan the move.

What is the difference between Vue 2 and Vue 3?

The practical difference is runtime, compiler, app setup, tooling, and ecosystem behavior. Composition API matters for new development, but migration planning should focus on breaking changes, dependencies, tests, and release risk.

Can I use Vuetify 2 with Vue 3?

Treat Vuetify 2 as a migration dependency, not a small package detail. Vuetify says Vuetify 2 has reached EOL and recommends planning a move to Vuetify 3 through its Vuetify 2 EOL notice.

Is Vue 3 faster than Vue 2?

Don’t sell the migration to leadership as a guaranteed speed win. Benchmark your own app before and after using the same routes, data, build mode, and hosting setup.

Can a Vue 2 to Vue 3 migration tool convert my app?

No safe one-click converter exists for a real product. Tools and codemods can change code patterns, but they cannot prove dependency support, Nuxt behavior, checkout flows, hydration, or production data cases.

Can I use Vue 2 components in a Vue 3 project?

Some ordinary Options API components may move with small changes. The risk is usually around plugins, global APIs, class components, UI libraries, state, slots, and old two-way binding patterns.

← All articles