Vue 2 to Vue 3 Migration: Stop Rewriting — Migrate Smart – SpiritCode.blog
Vue 2 to Vue 3 Migration: Stop Rewriting — Migrate Smart
Meta description: I migrated a 40-component Vue 2 codebase to Vue 3 Composition API without a full rewrite. Here’s my exact process, the real errors I hit, and what I’d do differently.
Last updated: June 2026
Introduction
Last year I inherited a Vue 2 codebase with 40+ components, a Vuex store, and exactly zero tests. The team wanted to migrate to Vue 3, and the first suggestion in the room was "let’s just rewrite it." I’ve been down that road before — the rewrite that takes six months, ships nothing, and eventually gets abandoned. Instead, I spent three weeks doing a Vue 2 to Vue 3 migration component by component, using Vue 3’s Composition API and the official migration build. The app is fully on Vue 3 now, the team prefers the new patterns, and we didn’t lose a single release cycle.
This guide covers the practical decisions I made, the migration order that worked, and the specific errors that cost me the most debugging time — so you don’t have to pay the same tuition.
TL;DR
Use the Vue 2.7 upgrade as an intermediate step — it backports Composition API to Vue 2 so you can migrate logic before migrating the framework.
Migrate leaf components first (no children, no Vuex bindings), then work up the tree toward pages and layouts.
The most common breaking change isn’t reactivity or lifecycle hooks — it’s the removal of $listeners, event bus patterns, and Vue.set(), which fail silently in unexpected ways.
Why Vue 2 to Vue 3 Migration Matters Now
Vue 2 reached end-of-life on December 31, 2023 . It no longer receives security patches, and the ecosystem — Vuex, Vue Router, Vetur — has largely moved on. If you’re still on Vue 2 in 2025, you’re accumulating technical debt with each passing month and running a framework with known unpatched vulnerabilities.
Vue 3’s Composition API isn’t just a syntax preference — it solves real problems. Logic sharing across components that previously required mixins (with all their implicit dependencies and namespace collisions) becomes explicit, testable composables. TypeScript support goes from "possible but painful" to first-class. And the performance improvements in Vue 3’s reactivity system are meaningful for complex UIs.
[INTERNAL LINK: related article — Vue 3 composables patterns for large codebases]
[SOURCE: https://v2.vuejs.org/eol/]
Prerequisites
Before migrating, you need:
Node.js 18+ and npm/yarn/pnpm
Your existing Vue 2 project with a working dev and build pipeline
Familiarity with Vue 2 Options API (this guide assumes you know data(), methods, computed, watch)
At least a basic read of the official Vue 3 migration guide
About 30 minutes per component for straightforward cases (plan more for Vuex-heavy components)
[SOURCE: https://v3-migration.vuejs.org/]
How to Migrate Vue 2 Components to Vue 3 Composition API
Step 1: Upgrade to Vue 2.7 First
Before touching Vue 3, upgrade to Vue 2.7 (vue@2.7.x). This release backports the Composition API — ref, reactive, computed, watch, onMounted — into Vue 2. That means you can start writing new code and refactoring old components using Composition API today, while still running the Vue 2 runtime.
npm install vue@2.7 @vue/composition-api<br># Note: In Vue 2.7, @vue/composition-api is no longer needed —<br># Composition API is built in. Uninstall it if present.<br>npm uninstall @vue/composition-api
Update any imports that used @vue/composition-api:
// Before (Vue 2 + @vue/composition-api plugin)<br>import { ref, computed } from '@vue/composition-api'
// After (Vue 2.7)<br>import { ref, computed } from 'vue'
This single step de-risks the entire migration. Your app keeps running. Your team learns Composition API incrementally. I spent two weeks here before touching Vue 3, and it was the right call.
Step 2: Audit Breaking Changes Before Writing a Single Line
Run vue-compat (Vue 3’s compatibility build) against your project and read the output before touching any component. The Vue migration build emits runtime warnings for deprecated APIs so you know exactly what needs to change.
npm install @vue/compat@3
In your vite.config.js or vue.config.js:
// vite.config.js<br>import { defineConfig } from 'vite'<br>import vue from '@vitejs/plugin-vue'
export default defineConfig({<br>plugins: [<br>vue({<br>template: {<br>compilerOptions: {<br>compatConfig: {<br>MODE: 2 // Run Vue 3 in Vue 2 compatibility mode<br>})<br>],<br>resolve: {<br>alias: {<br>vue: '@vue/compat'<br>})
Open your app and check the browser console. Every deprecation warning is a migration task. Build your list from these warnings — don’t guess.
Step 3: Migrate a Leaf Component — Side by Side
Start with the simplest component in your tree: no children, no Vuex, ideally just props and local state. Here’s a real example from my migration — a UserAvatar component:
Before (Vue 2 Options API):
export default {<br>name: 'UserAvatar',<br>props: {<br>userId: {<br>type:...