planned realistically, without underestimated pitfalls
Vue 2 has reached its end of life. The migration to Vue 3 is not a simple dependency update, it touches the reactivity system, the lifecycle, the build pipeline and many libraries at the same time. Teams that plan it realistically, instead of underestimating it, actually finish it.
Table of Contents
- 1. Starting point: what Vue 2 EOL really means
- 2. Inventory before the migration: what is affected
- 3. The real breaking changes, not just the obvious ones
- 4. The Vue Migration Build as a bridging strategy
- 5. From Options API to Composition API: when and how to switch
- 6. Libraries and their migration: Vuex, Vue Router, UI libraries
- 7. Testing strategy for the migration
- 8. A realistic timeline for different codebase sizes
- 9. Vue 2 vs. Vue 3: core differences at a glance
- 10. Summary
- 11. FAQ
1. Starting point: what Vue 2 EOL really means
Vue 2 reached its end of life in December 2023. Concretely, that means: no more security updates, no bug fixes, no new features. For applications already in production, this is not an immediate problem, Vue 2 keeps running and the browser notices nothing. The problem creeps in gradually: dependencies that keep evolving will stop being compatible with Vue 2. Build tools will stop supporting it. New hires will no longer have learned Vue 2 during their training. At some point the Vue 2 migration to Vue 3 stops being optional and becomes forced, and then under worse conditions.
Teams that voluntarily plan the Vue 3 migration now have control over the timing and the strategy. Teams that wait until a critical library drops Vue 2 support end up doing the migration under pressure, with little room for gradual adjustments. The good news: Vue 3 is a substantial improvement in most areas, a faster reactivity system, better TypeScript support, smaller bundle sizes. The investment pays off. The bad news: it really is a migration, not an update.
2. Inventory before the migration: what is affected
Before the first npm install vue@3 comes an inventory. This inventory is the most important step of the entire Vue 2 to Vue 3 migration and the one most often skipped. What belongs in the inventory: all Vue plugins and their Vue 3 compatibility. All UI libraries (Vuetify 2 to Vuetify 3 is its own large migration). All Vuex stores and their size. All Vue Router configurations. All tests and whether they mock Vue internals. All build tool configurations (Webpack to Vite, or keeping Webpack).
The result of the inventory is a migration matrix: which parts of the project are affected, how much, and in what order they need to be tackled. Typically it turns out that the biggest problem is not the Vue core API but a single UI library or an internal plugin that relies heavily on Vue internals. Identifying these blockers early and realistically estimating their migration effort prevents the most common mistake in a Vue migration project: hitting a wall after three weeks that was invisible at the start.
// Migration audit script, run before starting Vue 3 migration
// Scans package.json for known Vue-2-only packages
import { readFileSync } from 'fs'
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'))
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
// Packages that require attention during Vue 2 → Vue 3 migration
const migrationMap = {
'vuex': 'Replace with Pinia (recommended) or upgrade to Vuex 4',
'vue-router': 'Upgrade to vue-router 4, breaking API changes',
'vuetify': 'Vuetify 2 → Vuetify 3 is a significant separate migration',
'element-ui': 'Replace with Element Plus (Vue 3 fork)',
'vue-i18n': 'Upgrade to vue-i18n 9, composition API changes',
'@vue/test-utils': 'Upgrade to @vue/test-utils v2',
'vue-class-component': 'No Vue 3 equivalent, rewrite to Composition API required',
'vue-property-decorator': 'No Vue 3 equivalent, rewrite required',
'vuex-module-decorators': 'No Vue 3 equivalent, rewrite required',
}
Object.entries(migrationMap).forEach(([pkg, note]) => {
if (deps[pkg]) {
console.warn(`[MIGRATION REQUIRED] ${pkg}: ${note}`)
}
})
3. The real breaking changes, not just the obvious ones
The official Vue 3 migration documentation lists the breaking changes, but some are noticeably more painful in practice than others. The most important one: the global Vue object no longer exists. Instead, createApp() creates isolated application instances. That sounds harmless, but it breaks every plugin that extends Vue.prototype or calls Vue.use() globally. Many internal and commercial plugins rely on exactly these patterns, and they must either be replaced or adapted.
Another genuine source of pain: $listeners has been merged into $attrs in Vue 3. Code that uses v-on="$listeners" breaks silently, no error message, the event handlers simply stop working. This especially affects wrapper components that pass events through. The same applies to the .sync modifier, which was replaced by v-model:propName, and to $scopedSlots, which has been merged into $slots. These changes produce no compile-time errors and no runtime exceptions, they produce bugs that only surface during testing.
4. The Vue Migration Build as a bridging strategy
The official Vue Migration Build (@vue/compat) is a special Vue 3 build that emulates Vue 2 features with deprecation warnings. It makes it possible to migrate a Vue 2 application to Vue 3 gradually, first switch to the Migration Build, then migrate feature by feature until no more warnings show up in the build, and only then move to plain Vue 3. This is the recommended strategy for large codebases that cannot be migrated as a big-bang rewrite.
The limitations of the Migration Build are considerable, though, and are often underestimated. It is noticeably slower than plain Vue 3, not suitable for production use. It does not fully support every Vue 2 feature. Many libraries do not work correctly with it because they too access Vue 3 internals. The Migration Build is an analysis tool and a bridge, not a permanent state. Teams that use it should treat it as a time-boxed phase, not as a lasting solution for Vue 2 and Vue 3 coexisting.
// vite.config.ts, enable Vue Migration Build for gradual Vue 3 migration
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
compatConfig: {
MODE: 2, // emulate Vue 2 behaviour with deprecation warnings
},
},
},
}),
],
resolve: {
alias: {
// Redirect vue imports to the compat build
vue: '@vue/compat',
},
},
})
// main.ts, configure compat globally
import { createApp, configureCompat } from '@vue/compat'
import App from './App.vue'
// Enable all Vue 2 compat features, disable them one by one as you migrate
configureCompat({ MODE: 2 })
createApp(App).mount('#app')
5. From Options API to Composition API: when and how to switch
The Vue 3 migration does not require rewriting every component to the Composition API right away. Vue 3 continues to fully support the Options API, and it will keep doing so for the foreseeable future. The decision of when to move a component to the Composition API should be pragmatic: components that need to be touched anyway can be converted at the same time. Components that run stably and are never touched are allowed to stay in the Options API.
The Composition API delivers real value above all when logic is shared across multiple components. What used to require mixins in Vue 2, with all their naming conflicts, implicit dependencies and poor TypeScript support, can be expressed in Vue 3 as a clean composable with explicit inputs and outputs. Migrating mixins to composables is one of the few areas where the Vue 3 migration does not just make the code compatible, it measurably improves it.
6. Libraries and their migration: Vuex, Vue Router, UI libraries
Migrating state management is its own mini-project in most projects. Vuex 4 is the minimal option: it works with Vue 3 but keeps the Options-API-style syntax and all the known Vuex 4 limitations. Pinia is the recommended long-term strategy: lighter, better typed, no boilerplate for actions and mutations. Migrating from Vuex to Pinia can be done gradually, store by store, and both can temporarily coexist.
With UI libraries, the situation is often the most painful part of the entire Vue 2 to Vue 3 migration. Vuetify 2 to Vuetify 3 is its own extensive migration with renamed components, a new theme API and breaking changes in the grid syntax. Element UI was rebuilt as Element Plus for Vue 3, but it is not a drop-in replacement. Quasar and PrimeVue have Vue 3 versions with different API changes. Planning the UI library migration as its own phase, with its own testing period, is the buffer that is most often missing from the migration plan.
7. Testing strategy for the migration
Tests are both the most important safety net and a potential drag during the Vue 3 migration. The most important safety net: integration and end-to-end tests that run against the live application without mocking Vue internals. They largely survive the migration unchanged because they test the UI from the user's perspective. The potential drag: unit tests that use Vue internals directly, wrapper mounting with many manual mocks on Vue APIs, tests on specific emit structures, or direct access to component instances.
Migrating @vue/test-utils from version 1 to version 2 brings its own breaking changes: shallowMount semantics have changed, find and findAll behave differently, and many options were renamed. Realistic estimate: for a medium-sized Vue 2 application with good test coverage, the pure testing migration effort amounts to 20 to 30 percent of the total migration effort. Leaving this share out of the timeline is one of the most common reasons Vue migration projects take longer than planned.
8. A realistic timeline for different codebase sizes
An honest timeline for the Vue 2 to Vue 3 migration depends primarily on two factors: the number of components and the complexity of the libraries used. Small applications with under 50 components, few external libraries and good test coverage: 2 to 4 weeks with an experienced developer. Medium applications with 50 to 200 components, Vuex, Vue Router and a UI library: 2 to 3 months for a small team. Large applications with 200+ components, several Vuex modules, a complex UI library and legacy plugins: 4 to 8 months carried out in phases.
These estimates assume the migration runs alongside normal production operations, new features keep being developed in Vue 2 while the migration progresses on a parallel branch. That is the realistic situation at most companies. A complete feature freeze for the duration of the migration is rarely enforceable. Teams that build this realism into their planning actually complete the Vue 3 migration, teams that plan for a feature freeze usually fail on organizational enforcement.
9. Vue 2 vs. Vue 3: core differences at a glance
The differences between Vue 2 and Vue 3 run deeper than an API update. The table below shows the most important points for migration planning.
| Area | Vue 2 | Vue 3 | Migration effort |
|---|---|---|---|
| Reactivity | Object.defineProperty (array limits) | Proxy (complete, dynamic) | Mostly automatic, check Vue.set/delete |
| Global API | Vue.prototype, Vue.use(), Vue.mixin() | createApp(), app.use(), app.provide() | High, all plugins affected |
| State management | Vuex 3 (mutations, actions) | Pinia (stores, actions, no boilerplate) | Medium, gradual store by store |
| Fragments | Single root element required | Multiple root elements possible | Low, optional to use |
| TypeScript | Retrofitted (vue-class-component) | Native TS support, fully typed | High, rewrite decorator-based classes |
The biggest migration effort does not come from the Vue core API but from the ecosystem: plugins that use Vue.prototype, decorator-based class components, and UI libraries that have no Vue 3 version. These points must be fully identified during the inventory phase, not discovered at the first attempt to compile against Vue 3.
Mironsoft
Vue.js migration, frontend architecture and upgrade consulting
Migrating Vue 2 to Vue 3, with a realistic plan and an experienced team?
We carry out the inventory, identify the real blockers, and accompany the migration step by step, with a timeline that fits your production operations instead of fighting them.
Migration inventory
Complete analysis of all affected dependencies and blockers before the first code change
Step-by-step implementation
Migration Build, store-by-store Vuex to Pinia, parallel production operations
Honest timeline
Realistic, based on your codebase, no estimates without knowing the dependencies
10. Summary
A Vue 2 to Vue 3 migration is not a dependency update, it is a migration project with its own phases, risks and time requirements. The critical success factors: a complete inventory before the first code change, a realistic timeline that accounts for parallel production operations, and identifying the real blockers, usually plugins and UI libraries, not the Vue core API. The Vue Migration Build is a valuable analysis tool, but not a lasting solution.
The Composition API does not need to be used for every component immediately, the Options API remains fully supported in Vue 3. Pinia is the better long-term choice over Vuex 4, but the migration can happen gradually. Tests need their own migration time, @vue/test-utils v2 has breaking changes that are underestimated. With an honest timeline that accounts for these factors, teams complete their Vue migration without the last-minute surprises that arise from underestimating the complexity at the start.
Vue 2 to Vue 3 migration, the essentials at a glance
Inventory first
Check all plugins, UI libraries and Vuex stores for Vue 3 compatibility. The real blockers are rarely the Vue core API.
Migration Build as a bridge
@vue/compat emulates Vue 2 features with warnings, an analysis tool, not a production solution. Use it for a limited time.
Options API stays
Vue 3 fully supports the Options API. Introduce the Composition API gradually, for components that need to be touched anyway.
Plan for test time
@vue/test-utils v2 has its own breaking changes. Test migration costs 20 to 30 percent of the total effort, this share is regularly forgotten.