The UpgradeJS Blog

10 Steps to Migrate from Vue 2 to Vue 3

Active support for Vue 2 ended on March 18, 2022, and it is scheduled to reach its end of life on December 31, 2023. This means that, starting from 2024, there will be no more bug fixes or security patches provided. If your application is still running on Vue 2, it is essential to start preparing for the migration to version 3.

The Vue team has been considerate of developers who are transitioning from Vue 2 to Vue 3 by providing a helpful tool called the “migration build.” This build, available as a JavaScript package named vue/compat opens a new window , is specifically designed to assist teams in migrating their applications smoothly from version 2 to version 3.

With the migration build, you have the flexibility to migrate your application gradually, allowing you to ship updates to production without the need to complete the entire migration at once. It’s important to note that while using the migration build, there may be minor performance and bundle size trade-offs.

However, it’s worth considering that the Vue team has plans to discontinue publishing the migration build at some point in the future. Therefore, it is advisable to eventually move beyond the migration build to fully embrace the benefits of Vue 3.

Who is this post for?

In this post, we will guide you through the 10 steps required to successfully migrate from Vue 2 to Vue 3. However, it’s important to note that we are making a few assumptions along the way. These assumptions are based on common scenarios and best practices, but they may not cover every specific use case. It’s always recommended to thoroughly review and adapt the steps according to your application’s unique requirements.

If your application requires Internet Explorer 11 (IE11) support, it means you will need to continue using Vue 2. Vue 3 takes advantage of modern JavaScript features and optimizations that are not compatible with IE11’s older browser environment. Therefore, if IE11 compatibility is a critical requirement for your project, it is necessary to stick with Vue 2 for the time being. It’s important to evaluate your target audience and browser compatibility needs before making the decision to migrate to Vue 3.

Something to keep in mind is that the migration build (vue/compat) only supports the publically exposed Vue API. Migrating applications that rely on private APIs would require additional effort and consideration. It is necessary to identify and update any code that utilizes private APIs as an additional task before proceeding with the migration to Vue 3. Private APIs are subject to change or removal in different Vue versions, so it’s essential to review your codebase for any dependencies on these APIs and modify them accordingly to ensure compatibility with Vue 3.

Due to the intricacies involved in SSR, this article will not delve into the specific steps required to migrate the SSR portion of your application. SSR often involves additional dependencies, configurations, and specific implementation considerations, which may differ from one project to another.

We assume that the application you intend to upgrade is currently running on either Vue 2.6 or Vue 2.7, which already supports the new slot syntax. If your application is on an older version of Vue, it is recommended to first upgrade to Vue 2.7 or a compatible version that includes the new slot syntax updates. Once you have successfully updated the slot syntax, you can then proceed with the other steps outlined in the migration process.

Lastly, it’s important to consider that your application may have dependencies that are incompatible with Vue 3. As part of the migration process, you might need to replace or find Vue 3-compatible versions of those dependencies. It’s worth noting that this article will primarily focus on discussing the need to update two crucial dependencies: Vuex and Vue Router. However, please keep in mind that there may be other dependencies in your project that also require attention and updates to work seamlessly with Vue 3.

Strategy

For the upgrade strategy, we will begin by creating a new migration branch in our version control system.

Throughout the migration, it is important to stay up-to-date with the changes made to the production version of our application. To achieve this, we will frequently rebase our migration branch on top of the main branch. Rebasing (or merging) ensures that we incorporate the latest changes from the main branch into our migration branch, enabling us to work with the most recent version of the application while performing the migration.

As we proceed with the migration, we have the flexibility to decide whether to merge our migration branch into the main branch before completing the entire migration (after step 9) or only after making the complete switch to Vue 3 (after step 10). Both approaches have their merits and depend on the specific needs and circumstances of your project.

If we choose to merge the migration branch into the main branch before completing the entire migration, it allows for an incremental rollout of Vue 3 changes. This approach can help distribute the effort and mitigate the risks associated with a big-bang migration. However, it is crucial to conduct sufficient quality assurance testing during this process to ensure that the application functions as expected in the production environment. Testing should encompass both the existing features and any new features introduced during the migration.

Alternatively, we can decide to merge the migration branch into the main branch only once we have completed the entire migration to Vue 3. This approach ensures a cleaner separation between the Vue 2 and Vue 3 codebases, providing more clarity during the transition. Again, rigorous testing and quality assurance should be performed after the merge to verify the stability and functionality of the application.

In either case, adequate quality assurance testing is essential to ensure that the application works as expected in the production environment. This includes functional testing, regression testing, and potentially performance testing to identify and address any issues or regressions that may arise during the migration process.

At this stage, it is worth taking a look at the upgrade workflow from the Vue 3 migration guide opens a new window .

Step 0: Update slot syntax

Before proceeding with the Vue 2 to Vue 3 migration, it’s essential to update the slot syntax if we are still using the deprecated named / scoped slot syntax opens a new window in our application. The latest syntax, which is already supported in Vue 2.6 and later versions, should be adopted.

To demonstrate the difference, consider the following code excerpts that showcase the old slot syntax compared to the new syntax:

Old syntax (deprecated):

<template slot="greeting" slot-scope="slotProps">
  {{ slotProps.text }} ({{ slotProps.count }})
</template>

New syntax:

<template #greeting="{ text, count}">
  {{ text }} ({{ count }})
</template>

Sidenote: This is a preparation step, and in the spirit of indexes I decided to start my count at 0. This also means I can end with step number 10 (which is vital 🙂).

Step 1: Update Vue and add Vue-Compat

The first step in the migration process is to update Vue to the desired version and add the Vue-Compat package. As of now, the latest versions for both Vue and Vue-Compat are 3.3.4. It’s recommended to use the same version for both packages to ensure compatibility.

Additionally, you can remove the vue-template-compiler package since, starting from Vue 3.2.13, vue/compiler-sfc is bundled within the main vue package. This means there is no longer a need to install vue/compiler-sfc separately. (See @vue/compiler-sfc opens a new window )

Here’s a diff to illustrate the necessary changes:

"dependencies": {
-  "vue": "^2.7.14",
+  "vue": "^3.3.4",
+  "@vue/compat": "^3.3.4"
   ...
},
"devDependencies": {
-  "vue-template-compiler": "^2.7.14"
}

It’s important to note that at this stage, your application may break and your tests may fail. This is expected since there are additional steps to be taken before the app will run smoothly again. We will address these issues in the upcoming steps.

Step 2: Alias vue to @vue/compat

After installing @vue/compat as the migration build, the next step is to update your Webpack , Rollup, Vue-cli, or Vite configuration. The goal is to configure your build tool to alias vue to @vue/compat, so that whenever vue is referenced, it loads @vue/compat instead.

Make sure to review the documentation specific to your build tool for detailed instructions on how to configure the aliasing of vue to @vue/compat. Here are some specific notes for applications that use Webpack, but similar steps would be taken for other build tools as well.

Since we are making use of Webpack we need to upgrade vue-loader to version ^16.0.0. To update vue-loader with yarn you could run:

yarn upgrade vue-loader@^16.0.0

If we fail to update vue-loader it may result in a compile-time error similar to the following:

Module build failed: Error: Cannot find module 'vue-template-compiler'

To update vue-loader with yarn you could run:

yarn upgrade vue-loader@^16.0.0

If we fail to update vue-loader it may result in a compile-time error similar to the following:

Module build failed: Error: Cannot find module 'vue-template-compiler'

You can confirm that the update was successful by confirming that you see the following in your package.json file:

"vue-loader": "^16.0.0"

Keep in mind that vue-loader required webpack 4.1 and above to work.

"webpack": "^4.1.0 || ^5.0.0-0"

If you have an outdated version of Webpack, it’s necessary to update Webpack before continuing with the Vue 3 upgrade process. Updating Webpack may sometimes trigger a series of dependencies updates, which shows the importance of keeping dependencies up to date to avoid such situations in the future.

In your Webpack configuration file, you may have a line similar to this:

'vue$': 'vue/dist/vue.esm.js',

To update it for Vue 3 migration, you will modify it as follows:

'vue$': '@vue/compat/dist/vue.esm-bundler.js',

The $ sign is a convention used by Webpack to indicate an exact match when using the vue alias. This modification ensures that when vue is used in import paths across different modules, Webpack correctly resolves it to @vue/compat. This avoids confusion and enables the seamless usage of vue throughout your application.

For more information, you can refer to the Webpack documentation on resolve.alias: https://webpack.js.org/configuration/resolve/#resolvealias opens a new window

Step 3: Update transition class names

To ensure compatibility with Vue 3, it’s necessary to update the transition class names in your application. This update involves adding the -from suffix to the existing CSS class names related to transitions.

To perform this update, you can do a project-wide search for CSS class names ending with -enter and -leave. For each transition class found, modify it by appending the -from suffix. Typically, these changes will be made in the CSS files or relevant sections of your application.

Here is an example showing how .fade-enter is updated to .fade-enter-from.

- .fade-enter, .fade-leave-active {
+ .fade-enter-from, .fade-leave-active {

By updating the transition class names, you ensure compatibility with Vue 3’s transition system. The suffix -from indicates the initial state of the transition.

Note that you will not see any compile- or run-time errors if you skip this step. However this is a vital step in the process to ensure that transitions are not affected by the migration.

For a comprehensive understanding of the six transition class names and their usage, you can refer to the Vue documentation opens a new window . Additionally, you can explore the vue-hackernews example app opens a new window for detailed information on how to update the class names.

Step 4: Fix compile-time issues

After completing the previous steps and attempting to start your application locally, you may encounter compile-time errors or warnings. It’s important to address these issues before proceeding further with the migration process.

Based on the Vue-Compat migration guidelines, there are six known incompatibilities opens a new window between Vue 2 and Vue 3 that can lead to compile-time errors. We are required to fix these incompatibilities upfront to ensure a smooth migration. You can find more details about these incompatibilities in the Vue-Compat documentation opens a new window .

Issue 1: Mounted application does not replace the element it’s mounted to.

Inspect the rendered DOM, and pay close attention to the ID of the mounted vue app as well as the parent container. If you notice that the ID is identical then you would need to update one of the two IDs as well as Javascript or CSS references to that DOM node.

Before: Before: Mounted Vue application with duplicate id name. After: After: Mounted Vue application with renamed container id name.

Remember to thoroughly test your application after making these changes to verify that the Vue app is now mounted as expected and functions correctly.

Issue 2: Production devtools is now a build-time flag

You might see the following error message when you run the application.

Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined. You are
running the esm-bundler build of Vue, which expects these compile-time feature flags to be
globally injected via the bundler config in order to get better tree-shaking in the production
bundle.

For more details, see https://link.vuejs.org/feature-flags.

It is recommended that we properly configure the build-time flags in our build tool to ensure that our final build bundles efficiently. (Read more about build time flags. opens a new window )

If you are using Webpack the change might look like this:

const webpack = require('webpack');

module.exports = {
  plugins: [
    // Define Bundler Build Feature Flags
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true, // enable/disable Options API support
      __VUE_PROD_DEVTOOLS__: false // enable/disable devtools support in production
    }),
  ],
};

Issue 3: v-if and v-for precedence when used on the same element has changed

In Vue 3, there has been a change in the precedence of v-if and v-for directives when used on the same element. To address this breaking change, a simple solution is to wrap the v-for element inside a <template> element. Then, you can move the v-if directive to the new <template> element.

Here’s how you can adapt your code to accommodate this change: Before:

<div v-if="condition" v-for="item in items">
  <!-- Content here -->
</div>

After:

<template v-for="item in items">
  <div v-if="condition">
    <!-- Content here -->
  </div>
</template>

For more detailed information and additional insights into this breaking change, you can refer to the v-if and v-for precedence section opens a new window in the Vue 3 migration guide.

Issue 4: v-if branches can no longer have the same key

In Vue 3, it is no longer allowed for v-if branches to have the same key. This change ensures better rendering and behavior in the component tree. When encountering this issue, there are a few options available depending on your specific use case.

  1. Remove the keys: In some cases, you can completely remove the key attribute from the v-if branches. Vue will automatically assign keys based on its internal algorithm. This approach is suitable when the order and presence of the branches are stable and don’t rely on specific key values. Removing the keys simplifies the code and lets Vue handle the key assignment for you.
  2. Assign unique keys: If the v-if branches require explicit keys due to dynamic changes or specific requirements, you need to ensure that each branch has a unique key value. This approach guarantees that Vue can differentiate between the branches and perform accurate updates.

The migration guide provides more detailed information on the usage changes of the key attribute opens a new window and offers additional insights and examples.

Issue 5: <template v-for> key should now be placed on <template>

In Vue 3, there has been a change in the placement of the key attribute when using <template> with v-for. Unlike in Vue 2, where the key attribute was placed on the inner element within the <template>, in Vue 3, we can and should add the key directly to the <template> element itself.

Vue 2:

<template v-for="(item, index) in items">
  <div :key="index">
    <!-- Content here -->
  </div>
</template>

Vue 3:

<template v-for="(item, index) in items" :key="index">
  <div>
    <!-- Content here -->
  </div>
</template>

For more detailed information and additional insights into this breaking change, you can refer to the Vue 3 migration guide opens a new window .

Issue 6: <template functional> is no longer supported in single-file-components

In Vue 2, functional components provided a way to return multiple root nodes and were known for their performance advantages over stateful components. However, in Vue 3, the Vue team made the decision to remove the syntax for functional components.

To update your codebase, you need to remove the functional attribute from single-file components or the { functional: true } option if you used functional components with render functions . Simply removing this attribute or option will align your code with the Vue 3 syntax and behavior.

For more detailed information and further insights into this breaking change, I recommend referring to the Vue 3 migration guide opens a new window . It provides additional context and examples to help you transition smoothly from functional components in Vue 2 to the updated syntax in Vue 3.

Step 6: Fix run-time warnings

After addressing the compile-time errors and successfully running your application, it’s common to encounter a significant number of warnings during the migration process. These warnings may appear in both the command line output and the browser console.

To tackle these warnings effectively, it’s recommended to filter and prioritize them, focusing on one item at a time. This approach allows for a systematic resolution of warnings, starting with those originating from your own source code.

Global API Changes

To address the global API changes in Vue 3, specifically the introduction of createApp and the removal of the vm.$mount() method and the “el” option, you need to update your application code.

Here’s an example that demonstrates the necessary changes:

Vue 2 code:

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from './store'

import App from './App'

Vue.use(VueRouter)

const router = new VueRouter({
  routes // short for routes: routes
})

new Vue({
  router,
  store
}).$mount('#app')

Vue 3 code:

import Vue, { createApp } from 'vue'
import VueRouter from 'vue-router'
import store from './store'

import App from './App'

const router = new VueRouter({
  routes // short for routes: routes
})

const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')

In the Vue 3 code, we import the createApp function from the vue package and use it to create the Vue app instance. We then use the mount() method on the app instance to mount the app to the DOM element with the ID of “app”.

By making this change, you’ll eliminate the warning message regarding the global app bootstrapping API.

[Vue warn]: (deprecation GLOBAL_MOUNT) The global app bootstrapping API has changed: vm.$mount
() and the "el" option have been removed. Use createApp(RootComponent).mount() instead.

Be sure to update all relevant parts of your application that use the deprecated global instance API to follow the new createApp approach.

It’s also worth mentioning that the migration guide provides additional information on optimizations opens a new window and other global API changes. opens a new window . It’s recommended to review the migration guide thoroughly to ensure that you address all necessary updates and improvements during the migration process.

In Vue 2 the configuration changes we made would modify the base Vue instance. This is clear when we consider the use of Vue.use and Vue.directive. The effects of these base instance modifications were more pronounced whenever we wrote tests and we were required to restore global configuration after each test.

Keep in mind that updating the global API usage may require adjustments in your tests and testing frameworks as well. Be sure to update your testing code accordingly to align with the new Vue 3 API and configuration changes.

Address removed APIs

When addressing the removed APIs in Vue 3, there are several changes that need to be considered. Here’s a summary of the removed APIs and their alternatives:

Key code event modifiers are no longer supported in Vue 3. If your application uses key code event modifiers, you’ll need to convert them to their named modifier equivalents. You can refer to the migration guide for more details opens a new window on how to handle this change.

$on, $off, and $once instance methods have been removed in Vue 3. If you used them to implement a global event bus, you’ll need to find an alternative approach. One option is to use third-party libraries like mitt opens a new window  or tiny-emitter opens a new window to implement an event bus.

Here’s a simple example demonstrating how to replace the Vue 2 component event behavior with the use of tiny-emitter:

// eventBus.js
import emitter from 'tiny-emitter/instance'

export default {
  $on: (...args) => emitter.on(...args),
  $once: (...args) => emitter.once(...args),
  $off: (...args) => emitter.off(...args),
  $emit: (...args) => emitter.emit(...args)
}
// main.js
import eventBus from './eventBus.js'
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
// This makes `$bus` available inside any component template in the application,
// and also on `this` of any component instance:
app.config.globalProperties.$bus = eventBus
app.mount('#app')

In the Vue 3 code above, we created an event bus. Then, we use createApp to create the Vue app instance and assign the emitter instance to a global property we calld $bus. This allows components to access and use the emitter for event communication. Read more about global properties in the Vue API guides opens a new window .

export default {
  mounted() {
    this.$bus.$emit('some-event')
  }
}

Filters have been removed in Vue 3 opens a new window . To achieve similar functionality, you can use computed properties or methods in your components. Using computed props instead of globally defined filters could lead to unnecessary code duplication. To combat code duplication we can use mixins or the composition API opens a new window .

Defining inline templates, as described in the Vue 2 documentation opens a new window , has been removed in Vue 3. You’ll need to restructure your code to use separate template files or the render function for component templates.

The $children instance property, which provided direct access to a component’s child components, has been removed in Vue 3. If your code relies on this property, you’ll need to refactor it to use other techniques, such as component references or scoped slots.

The propsData option, which allowed passing props to a root component during instantiation, has been removed in Vue 3. Instead, you should use the props option when creating the root component using createApp opens a new window .

Breaking change to render function API

If your application utilizes the render function API, whether through custom render functions or renderless components, it’s important to be aware of the changes made to the render function API in Vue 3.

Notable changes to the render function API include:

To see a practical example of updating a render function, you can refer to the Vue hacker news example app. The provided commit opens a new window in the Vue hacker news repository demonstrates how to update a render function effectively.

Be sure to thoroughly review the Vue 3 migration guide for comprehensive information on the render function API changes, enabling a smooth transition and addressing any specific breaking changes in your application.

Many other changes

We understand that there may be numerous warnings generated during the migration process. Rather than listing every possible warning, we will outline a general approach for removing these warnings:

  1. Start by identifying the specific warning message you encounter. The warning message can often provide hints about the necessary changes.
  2. Refer to the feature reference list provided in the Vue Compatibility Build documentation opens a new window . Each item on the list describes the required changes or provides a link to the relevant section of the Vue 3 migration guide opens a new window .
  3. Follow the suggestions and instructions provided in the feature reference list and the migration guide to address the specific warnings. Apply the recommended changes to your codebase accordingly.
  4. It’s worth noting that some warnings might be generated by outdated dependencies, such as Vuex or Vue Router. To resolve these warnings, make sure to update your Vuex and Vue Router dependencies to their compatible versions for Vue 3.

By following these steps and carefully addressing the warnings one by one, you can effectively mitigate the issues and ensure a successful migration to Vue 3.

Step 7: Upgrade Vuex

When migrating to Vue 3, it’s important to upgrade Vuex from version 3 to version 4, as Vuex 3 is not compatible with Vue 3. Fortunately, the process of upgrading from Vuex 3 to Vuex 4 is relatively straightforward. Most of the APIs remain unchanged, but there are a few breaking changes that need to be addressed before proceeding.

One significant change is that we no longer pass Vuex to Vue during the installation step. Instead , we now pass our store directly to our app using the app.use() method:

// Example main.js

import { store } from './store'
import App from './App.vue'

const app = createApp(App)

app.use(store)

Vuex 4 also provides improved TypeScript support, which you can learn more about in the TypeScript Support opens a new window section of the Vuex documentation.

To understand the full Vuex migration process and explore the new features introduced in Vuex 4, refer to the official Vuex migration guide: Migrating to 4.0 from 3.x opens a new window .

For a real-world example of upgrading to Vuex 4, you can examine the vue-hackernews example app and review the commit that demonstrates the migration: Vue HackerNews Example - Vuex 4 Upgrade opens a new window .

Step 8: Upgrade Vue-Router

As part of the Vue 3 migration, it is necessary to upgrade to Vue-Router v4. This new version of Vue-Router introduces several breaking changes that we need to be aware of in order to successfully complete the Vue 3 migration.

One significant change is the introduction of the createRouter function, which replaces the previous usage of new Router(). The Vue-Router migration guide opens a new window provides detailed information on how to import and use the createRouter function in Vue 3.

Vue-Router 4 introduces a number of changes that may impact your application, including the removal and replacement of certain features as well as TypeScript changes. It is important to thoroughly review the Vue-Router migration guide opens a new window to ensure a smooth migration process.

To see practical examples of the changes involved in upgrading Vue-Router, you can refer to the vue-hackernews example commit opens a new window provided by the Vue team. This commit demonstrates the specific modifications typically required when upgrading Vue-Router.

Step 9: Update Typescript configuration

With the migration to Vue 3, it is important to review and update the TypeScript configuration in your project to ensure compatibility and take advantage of new features.

Vue 3 introduces the defineComponent wrapper function, which enhances component creation with improved type inference support. The Vue 3 TypeScript guide opens a new window provides comprehensive information on utilizing TypeScript in Vue applications, including the usage of defineComponent.

To ensure a smooth migration, it is recommended to review your TypeScript configuration and update it to align with the Vue 3 requirements and best practices. This may involve adjusting compiler options, updating type definitions, or making changes to typings within your project.

Step 10: Switch to Vue 3

Up to now we methodically addressed the conflicting behavior between Vue 2 and Vue 3. We also addressed changes due to compile-time errors, removed APIs and new features added in Vue 3.

Once all warnings are fixed and your codebase is ready, you can remove the migration build and switch to using Vue 3 directly. However, it’s important to note that if your project has dependencies that still rely on Vue 2 behavior, you may not be able to switch to Vue 3 immediately.

To switch from the “migration build” to the official Vue 3 build we would need to update the vue alias in our build tool. We can update the vue alias by adding the following line to our Webpack configuration:

  'vue$': 'vue/dist/vue.esm-bundler.js',

Essentially we are updating the same file we changed in step 1 of this article.

We can switch our application over one component or feature at a time by making use of the configureCompat function provided by the vue-compat.

import { createApp, configureCompat } from 'vue'

configureCompat({ })

Conclusion

Upgrading to Vue 3 is not just about keeping up with the latest version of the framework; it’s about embracing a more efficient and powerful development experience. With Vue 3, you gain access to new features like the composition API, improved performance optimizations, and enhanced TypeScript support.

Upgrading from Vue 2 to Vue 3 can seem like a daunting task with numerous breaking changes and migration steps. However, with careful planning and following the migration guide provided by the Vue team, the process can be manageable and rewarding.

Please get in touch with us if you need support or advice with your next Vue migration.