🧱 Migration Guide: Vue2/Nuxt2/Vuetify2 to Vue3/Nuxt3/Vuetify3
In this blog, I will share my experience and insights gained while migrating the project based on an existing codebase, from tackling Nuxt 3 updates to addressing Vue 3 breaking changes and adapting to Vuetify 3. I navigated through numerous hurdles and discovered effective solutions, as a result, I will also share the challenges encountered and the strategies employed.
Background
Before going ahead with the migration, I extensively researched various technical blogs and almost all of them recommended migrating the code after creating a new project, cause there might be too many errors🐞 and could not figure out which module was causing the error or if it was something else.
But after weighing the pros and cons, I made the decision to upgrade the project based on our old codebase, mainly because our codebase already uses the composition api syntax by @nuxt/composition-api using TypeScript, and have also introduced auto-imports. It proved to be a very bold decision💥, as I was not able to validate the changes I made for quite some time, and in fact I did not actually get the project up and running until after I had completed most of the nuxt 3 and vue 3 migrations. But fortunately, I did succeed 🎉.
Before Migration
- Learn about Nuxt 3
- It allows to experience TypeScript with zero configuration.
- Nuxt 2 uses webpack 4 and Babel, Nuxt 3 switched to Vite (or webpack 5) and esbuild. Vite is the default choice but you can still use webpack 5.
- It uses Nuxi for Command line interface. It is like the Vue CLI but for Nuxt.
- It uses Nitro as the server engine, offering cross-platform support for Node.js, browsers, service workers and more.
- It uses Nuxt Kit that provides users with a new flexible module development experience with cross-version compatibility.
- It uses Nuxt Bridge to ease the migration from Nuxt 2.
- Prerequisites Node.js - v16.10.0 or newer
- Learn Vue 3 Composition API
- As Nuxt 3 is built on Vue 3, understanding the changes and enhancements in Vue 3 is essential. Pay attention to the Composition API, which offers a more flexible and powerful way to organize and manage your code.
- Learn about TypeScript
- Nuxt 3 is written in TypeScript, and having a basic understanding of TypeScript will be beneficial. Being able to read and understand TypeScript code will help you navigate through Nuxt 3’s source code and address any potential issues that may arise during the migration process.
- Migrating your code to TypeScript is highly recommended due to its enhanced type safety, improved developer experience with autocompletion and type inference, early detection of errors during development, robust tooling support, and its ability to maintain code scalability and readability as projects grow in complexity. TypeScript adds clarity and structure to the codebase, ultimately leading to more reliable and maintainable applications.
- Learn about Modules
- Nuxt 3 requires ECMAScript modules (ESM), which may require converting CommonJS modules (CJS) used in your project. Learn about the differences between these module systems and how to migrate your modules to ESM.
- Try to set up a demo project
- 👍👍👍 Having a demo project before migration is an excellent idea, especially when dealing with complex and large-scale projects. Here’s an example demo project including Vue 3, Nuxt 3, Vuetify 3, Pinia, pwa and firebase authentication: 👉 github repo, see more details in this blog.
- It allows you to experiment and isolate specific packages or modules, helping you identify the source of issues more effectively. When facing challenges in the migration process, the demo project serves as a safe playground to troubleshoot and find solutions without impacting the main project.
- This experimental approach fosters a deeper understanding of potential pitfalls and ensures a smoother migration for the main project, ultimately saving time and minimizing risks.
Resources
📖 Keeping the migration guide open and referring to it throughout the process will help you avoid common pitfalls and ensure a smoother transition.
Vue 3
Nuxt 3
Vuetify 3
Migration Plan
0. Abstract reusable components
- Abstract recurring code patterns into reusable components.
1. Nuxt 3 Migration
- Update Nuxt 3 configurations.
- Adapt the codebase to address breaking changes introduced in the latest version.
2. Package Upgrades and Replacements
- Upgrade package versions to their Vue 3-compatible equivalents.
- Identify and replace packages that may not be compatible with Vue 3 or have alternative solutions available.
3. Integration of New Configurations
- Integrate the updated router, i18n, pinia, pwa, firebase, and other configurations to align with Nuxt 3.
4. Vue 3 Migration
- Begin the migration by updating the project’s codebase to work with Vue 3.
- Rewrite all components using the “script setup” syntax introduced in Vue 3.
5. Test Project Setup
- Attempt to run the project.
- Address any issues or conflicts that may arise during the testing phase.
6. Vuetify 3 Migration
- Follow the migration guide to adjust the code syntax of each component.
- Integrate the latest theme configuration.
- Explore alternative solutions for components that have not been fully migrated yet.
7. Style Restoration
- Restore any customized styles or appearance adjustments to maintain the project’s original look.
Nuxt 3 Changes Checklist
To get the project set up as quickly as possible, I focused on the nuxt 3 migration firstly. Here is a checklist outlining the major changes required. Nuxt 3 introduces several significant changes and improvements that require careful consideration during the migration process. In this section, I will present a checklist detailing the key modifications needed to adapt the project to Nuxt 3.
Configuration
- nuxt.config
- Migrate to the
defineNuxtConfig
function. - Migrate
router.extendRoutes
to the newpages:extend
hook.
- Modules
- Move all
buildModules
intomodules
. - Check for Nuxt 3 compatibility of modules. [we will do it later]
- TypeScript
- Add
"extends": "./.nuxt/tsconfig.json"
totsconfig.json
.
Auto Imports
- Nuxt Auto-imports
- Nuxt auto-imports functions and composables to perform data fetching, get access to the app context and runtime config, manage state or define components and plugins.
- Vue Auto-imports
- Vue 3 exposes Reactivity APIs like ref or computed, as well as lifecycle hooks and helpers that are auto-imported by Nuxt.
Meta Tags
- In
nuxt.config
, renamehead
=>meta
. - Access the component state with
head
=>useHead
.
Plugins and Middleware
- Plugins
- Migrate plugins to use the
defineNuxtPlugin
helper function. - Remove entries in
nuxt.config
plugins array that are at the top level of plugins/ folder.
- Route Middleware
- Migrate route middleware to use the
defineNuxtRouteMiddleware
function. - Reference route middleware using
definePageMeta
. - File name with extension
.client
and.server
.
Pages and Layouts
- Layouts
- Replace
<Nuxt />
with<slot />
. - Use
definePageMeta
to select the layout used by the page. - Move
~/layouts/_error.vue
to~/error.vue
.
- Pages: Dynamic Routes
- Replace
_id
with[id]
to define a dynamic route parameter. - Replace
_.vue
with[...slug].vue
to define a catch-all route.
Runtime Config
- Add environment variables to the
runtimeConfig
property of thenuxt.config
. - Migrate
process.env
touseRuntimeConfig
. - Exposing Runtime Config using
runtimeConfig.public
.
Build Tooling
- Remove
@nuxt/typescript-build
and@nuxt/typescript-runtime
from dependencies and modules. - Remove any unused babel dependencies.
- Remove any explicit core-js dependencies.
- Migrate
require
toimport
.
Server
- Any files in
~/server/api
and~/server/middleware
will be automatically registered, so you can remove them fromserverMiddleware
array.
Vue 3 Changes Checklist
Vue 3 comes with several breaking changes compared to Vue 2, which may necessitate adjustments to the project's existing codebase. In this section, I will provide a checklist of these breaking changes and the corresponding solutions or workarounds.v-model
- replace
.sync
withv-model
.
nuxt2:<ChildComponent :title.sync="pageTitle" />
nuxt3:<ChildComponent v-model:title="pageTitle" />
- for all v-models without arguments, make sure to change props and events name to
modelValue
andupdate:modelValue
respectively.
key
- no longer recommend using the key attribute on v-if/v-else/v-else-if branches, since unique keys are now automatically generated on conditional branches if you don’t provide them.
- when using
<template v-for>
with a child that usesv-if
, the key should be moved up to the<template>
tag.
v-if vs. v-for Precedence
- If used on the same element,
v-if
will have higher precedence thanv-for
.
nuxt2:v-for
would take precedence.
nuxt3:v-if
would take precedence.
v-bind Merge Behavior
- Order of bindings for
v-bind
will affect the rendering result.
template:<div id="red" v-bind="{ id: 'blue' }"></div>
result:<div id="blue"></div>
template:<div v-bind="{ id: 'blue' }" id="red"></div>
result:<div id="red"></div>
v-on.native modifier removed
- The
.native
modifier forv-on
has been removed. - Remove all instances of the
.native
modifier. - Ensure that all your components document their events with the emits option.
Render Function API
h
is now globally imported.- No domProps, the entire VNode props structure is flattened.
Slots Unification
this.$slots
now exposes slots as functions.this.$scopedSlots
is removed.- Replace all
this.$scopedSlots
occurrences withthis.$slots
in 3.x. - Replace all occurrences of
this.$slots.mySlot
withthis.$slots.mySlot()
.
Inline Template Attribute
- Support for the inline-template feature has been removed.
$children
$children
instance property has been removed and is no longer supported.
$listeners removed
$listeners
has been removed.- Event listeners are now part of
$attrs
. - Remove all usages of
$listeners
.
Events API
$on
,$off
and$once
instance methods are removed.- Component instances no longer implement the event emitter interface.
What’s New in Vue 3
Async Components
defineAsyncComponent
helper method that explicitly defines async components.- component option renamed to loader.
- Loader function does not inherently receive resolve and reject arguments and must return a Promise.
emits Option
- For components that re-emit native events to their parent, this would now lead to two events being fired.
Vuetify 3 Changes Checklist
It is essential to address the changes in Vuetify 3. However, it is important to note that Vuetify 3 is still under development, and some components may not be fully available yet. Here’s a checklist of the changes and updates.
A => B means A has been renamed to B.
v-expansion-panel
v-expansion-panel-header
=>v-expansion-panel-title
.v-expansion-panel-content
=>v-expansion-panel-text
.
v-menu
- offset-y, offset-x props =>
offset
prop
v-img
contain
has been removed and is now the default behavior. Usecover
to fill the entire container.
v-tabs
v-tab-item
has been removed, usev-window-item
.
Input components
validate-on-blur
prop has been renamed tovalidate-on="blur"
.- Variant props filled/outlined/solo have been combined into
variant
prop.
v-select/v-combobox/v-autocomplete
v-model
not present in items will now be rendered instead of being ignored.item-text
=>item-title
- The item slot will no longer generate a
v-list-item
component automatically, instead a props object is supplied with the required event listeners and props.
v-list
v-list-item-icon
andv-list-item-avatar
have been removed, usev-list-item
with icon or avatar props, or useappend
orprepend
slot.v-list-item-content
has been removed, use CSS grid for layout now instead.v-subheader
=>v-list-subheader
.
v-form
validate()
now returns aPromise<FormValidationResult>
instead of a boolean.
v-checkbox/v-radio/v-switch
input-value
=>model-value
.
v-btn/v-btn-toggle
fab
is no longer supported.flat
,outlined
,text
,plain
props have been combined intovariant
prop.
Layout
stateless
,clipped
,clipped-right
andapp
props have been removed fromv-navigation-drawer
,v-app-bar
andv-system-bar
.
v-data-table
- Should add
items-per-page-text="Rows per page:"
to change the text. item-key
=> addid
to items.- In items array
text
=>title
,value
=>key
. - Slot
expanded-item
=>expanded-row
,header
=>columns
.
Modules Compatibility
Ensuring the compatibility of third-party modules and plugins is crucial for a seamless transition. By upgrading, removing, or replacing modules as needed, we can maintain the project’s functionality and avoid potential conflicts.
For the modules named start with @nuxt, you can search for it here.
- Modules Removed
- @nuxtjs/eslint-config-typescript removed
- @nuxtjs/composition-api removed
- @nuxt/typescript-build removed
- @nuxtjs/axios removed
- @nuxt/types removed
- vue-i18n removed
- vuexfire removed
- vue-server-renderer removed
- vue-template-compiler removed
- Modules Replaced
- vue-chartjs => vue-chart-3
- vue-apexcharts => vue3-apexcharts
- vue2-perfect-scrollbar => vue3-perfect-scrollbar
- vue-jest => @vue/vue3-jest guide
- pinia-plugin-persistedstate => pinia-plugin-persistedstate/nuxt guide
- @nuxtjs/pwa => @vite-pwa/nuxt guide
- Modules Upgraded
- nuxt => ^3.5.2
- vue => ^3.3.4
- pinia => ^2.1.3
- vuetify => ^3.3.3 guide
- @nuxtjs/i18n guide
- @vueuse/nuxt => ^10.1.2 guide
- @pinia/nuxt => ^0.4.11
Challenges and Solutions
During the migration, I encountered several challenging issues that were not readily addressed in the official documentation. To find solutions, I proactively sought assistance from various sources, including the GitHub repository issues, the official Discord community, and Stack Overflow. In some cases, I even delved into the source code to gain a deeper understanding. Here are some of the questions I encountered and the corresponding answers I discovered.
❓ Refactoring Options API to Composition API
While the Options API remains functional in Vue 3, leveraging the Composition API offers significant advantages, especially for larger codebase. The Composition API is a built-in feature in Vue 3 and is also available in Vue 2 through the @vue/composition-api plugin.
Let’s see a basic example to understand the difference of coding structure between Options API and Composition API, the specific code images reference is from here: Arcana Network Blog.
1. Code in the Options API
2. Code in the Composition API
3. Simplified the above code by using the <script setup>
syntax
❓ How to inject in context in Nuxt 3
In Nuxt 2, we used to use useContext
to access the Nuxt context within the composition API, such as:
1 | // plugins/hello.js |
In Nuxt 3, we can access runtime app context within composables, components and plugins, such as:
1 | // plugins/hello.js |
❓ How to use dynamic components in Vue 3
In Vue 2, we define the dynamic components in the components
property, such as:
1 | export default { |
In Vue 3, we can use defineAsyncComponent
function, which accepts a loader function that returns a Promise.
1 | const MyComponent = defineAsyncComponent(() => import('my-component')) |
❓ How to use labs components in Vuetify 3
1. Way 1: Import and bootstrap v-data-table
in your component:
1 | <script setup> |
2. Way 2: Make the component available globally by importing it in Vuetify plugin.
1 | import { createVuetify } from 'vuetify' |
❓ How to define themes in Vuetify 3
Define the themes in plugins/vuetify file, light and dark are pre-installed in Vuetify 3, and you can change the color of them as following:
1 | // plugins/vuetify.ts |
To get the ‘something’ color under colors, you can do like this:
1 | .your-class-name { |
To change the theme by clicking, you can do like this:
1 | <template> |
❓ How to migrate useFetch from @nuxt/composition-api to Nuxt 3
In Nuxt 2, I use useFetch API from @nuxt/composition-api
, like this:
1 | <template> |
In Nuxt 3, there are many replacements, such as useAsyncData
and useFetch
that are auto-imported in Nuxt 3 project.
1 | <template> |
What’s more, we can define options in useAsyncData
including lazy
, transform
, watch
. See more here.
❓ Error: v-on with no argument expects an object value
I got this warning in console when page first rendered, and it led to the problem that the menu doesn’t show when button is clicked, same problem for v-tooltip. After diving into the Vuetify 3 documentation, I found the solution in the upgrade guide, there’s a general change for components:
- Activator slots work slightly different.
- Replace
#activator={ attrs, on }
with#activator={ props }
, then removev-on="on"
and replacev-bind="attrs"
withv-bind="props"
.Related issue: here1
2
3
4
5
6
7
8
9
10
11
12
13// before
<v-tooltip>
<template #activator="{ on, attrs }">
<span v-on="on" v-bind="attrs">{{ title }}</span>
</template>
</v-tooltip>
// after
<v-tooltip>
<template #activator="{ props }">
<span v-bind="props">{{ title }}</span>
</template>
</v-tooltip>
❓ Error: Transition renders non-element root node that cannot be animated
Just keep there is only one root element.
1 | <template> |
❓ Error: Cannot read _leaveCb property
This error happened when using watch to refresh the data and assign a value to template. Both of these give an error, but disappeared if remove watch
:
watch(props, refresh);
.- add
watch: [props]
withinuseAsyncData
.
I have tried all the solutions mentioned in similar issues, but only one of them works for me: Add <ClientOnly>
around the component.
Related issues: nuxt issue, vue-router issue.
❓ Error: localStorage is not defined in nuxt server-side
When you use SSR you don’t have access to the browser storage, localStorage
can only work on client side when process.client
is true.
Lessons Learned
Conclusion
The migration from Vue 2/Nuxt 2/Vuetify 2 to Vue 3/Nuxt 3/Vuetify 3 has been a challenging but highly rewarding journey. By choosing to upgrade the project from its existing codebase, I was able to preserve valuable configurations, logic, and functionality, saving time and effort that would have been required to start from scratch.
Throughout the migration process, I have navigated through various aspects, including Nuxt 3 changes, Vue 3 breaking changes, Vuetify 3 updates, and package adjustments. By leveraging the official documentation, seeking support from my mentor 💪, and also the discussion form the community like GitHub, StackOverflow, and Discord, I overcame obstacles and found solutions to intricate problems.
Migrating a project of this scale required patience, perseverance, and attention to detail. At times, I faced moments of frustration and uncertainty 😢, but with determination and the guidance of my mentor, I stayed focused on the goal 🎯. The adoption of TypeScript in the Nuxt 3 project further enhanced code safety and maintainability, ensuring a more robust codebase. 🔝
As I progressed through each migration phase, I carefully integrated new versions, refactored components using the Composition API, and verified functionality through rigorous testing. This meticulous approach ensured a successful transition to Vue 3 and Nuxt 3, unleashing the full potential of their features.
This migration project has taught me valuable lessons in staying adaptable, seeking support, problem-solving, and the importance of continuous learning to keep our projects at the forefront of web development. 👍 And thanks again for my mentor R.J. for his unwavering support, guidance, and belief in my abilities, without which this achievement would not have been possible. I’m truly grateful for his time and encouragement throughout this remarkable journey. 😄
References:
- Nuxt 3 Migration Simplified: A Cheat Sheet
- Vue3 / Nuxt3 / Vuetify3 Migration Steps
- Migrating from Nuxt 2 to Nuxt 3
- Vue 3 Composition API: Basics and Patterns
- 100 Things You Should Know About Nuxt 3
- Vuejs 3: Migrating from Options to Composition API
- Mastering Nuxt 2 Course and Mastering Nuxt 3 Course.
- Nuxt 3 with SSR on Google Cloud Firebase Functions (2023)
- Deploy Nitro apps to Firebase.
🔍 Check out the demo project in github.
📮 If find any errors, please feel free to discuss and correct them: biqingsue@[google email].com.