Vue Router is the official router for Vue: it maps each URL to the component that should render for it. Version 5.0 moves file-based routing, which used to live in a separate package called unplugin-vue-router, into vue-router itself. Apart from one change to the build that pages load from a CDN, nothing breaks. The release notes call it a "boring" release, and they mean it as a compliment. Two patches followed: 5.0.1 on 30 January and 5.0.2 on 2 February, so 5.0.2 is the one to install.
Apps on unplugin-vue-router drop that dependency and change some import paths. Two other groups should not just bump the version: Nuxt apps, which choose their router version themselves, and pages that load the CDN build with a script tag and use the devtools there.
What shipped
The release notes and the migration guide list these changes:
- unplugin-vue-router is merged into
vue-router. The Vite plugin is imported fromvue-router/vite, the generated routes fromvue-router/auto-routes, and the plugins for webpack, Rollup and esbuild, with their helper functions, fromvue-router/unplugin. - Data loaders, a way to fetch a page's data as part of the navigation, ship as experimental under
vue-router/experimental. A Pinia Colada flavour sits undervue-router/experimental/pinia-colada. - The Volar plugins move into the package as
vue-router/volar/sfc-typed-routerandvue-router/volar/sfc-route-blocks. Volar is the engine behind the Vue editor extension andvue-tsc, and these plugins teach it about your routes. - A JSON schema for the
<route>custom block that a page component can contain. - The devtools integration moves to version 8 of
@vue/devtools-api. - One bug fix you might notice: navigation guards now run when a component cached by
<KeepAlive>is reactivated for a different route.
The two patches are small. 5.0.1 makes the typed-router Volar plugin work with vue-tsc, and 5.0.2 removes the devtools code from the IIFE build and loosens a version check.
Upgrade an app with a routes array
If you write your routes by hand in a routes array and pass it to createRouter(), the guide has one line for you: no breaking changes. Update the dependency and you are done:
pnpm update vue-router@5
With npm the command is npm install vue-router@5. The package still asks for Vue 3.5 or newer, the same peer range as 4.6, so any project that runs 4.6 can install 5.
There is one exception, and most apps will never meet it. The IIFE build, the single file you load from a CDN with a plain <script> tag that puts a global VueRouter on the page, no longer bundles @vue/devtools-api. Version 8 of that package does not publish an IIFE build of its own, so the router cannot include it. Projects built with Vite use the ES module build and are not affected.
Set up file-based routing from the core package
File-based routing means the folder of page components is the route table. Add src/pages/about.vue and the URL /about exists, with no array to edit. Before 5.0 you installed unplugin-vue-router for this. Now the Vite plugin comes from the package you already have:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueRouter from 'vue-router/vite'
export default defineConfig({
plugins: [
VueRouter({
dts: 'src/route-map.d.ts',
}),
// VueRouter() must come before vue()
vue(),
],
})
The order matters. VueRouter() reads and transforms page components before the Vue plugin compiles them, so it has to be listed first.
The dts option names the file where the plugin writes the types for your routes. The migration guide recommends src/route-map.d.ts because most setups already type-check everything under src/, and a create-vue project's tsconfig.app.json does. Leave the option out and the plugin writes typed-router.d.ts at the project root, which you then have to add to include yourself.
The plugin reads src/pages by default and turns each file name into a path:
src/pages/index.vuerenders at/.src/pages/about.vuerenders at/about.src/pages/users/index.vuerenders at/users.src/pages/users/[id].vuerenders at/users/:id, andidbecomes a route param.src/pages/[...path].vuematches every URL nothing else matched, which is where a 404 page goes.
The router file stops listing routes and imports them instead:
import { createRouter, createWebHistory } from 'vue-router'
import { routes, handleHotUpdate } from 'vue-router/auto-routes'
export const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})
if (import.meta.hot) {
handleHotUpdate(router)
}
handleHotUpdate lets the dev server swap the routes when you add or rename a page, without a full reload.
The other conventions, such as nested routes, folders in parentheses that group pages without changing the URL, and named views, are documented on the unplugin-vue-router site. That is still where the file-based guide lives for 5.0; the router docs link to it.
Typed routes and params
The plugin also writes a route map: every route name, its path and its params. A route's name is its file path inside src/pages, so the user page above is called /users/[id]. Pass that name to useRoute() and its params are typed:
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute('/users/[id]')
const nextId = computed(() => Number(route.params.id) + 1)
</script>
<template>
<h1>User {{ route.params.id }}</h1>
<RouterLink :to="{ name: '/users/[id]', params: { id: nextId } }">
Next user
</RouterLink>
</template>
What TypeScript now checks in this page:
route.params.idis astring, and reading any other param, such asroute.params.slug, is an error.- The
RouterLink'stois checked against the same map, so a typo such asname: '/user/[id]'failsvue-tscinstead of failing in the browser. - Params you pass in accept numbers as well as strings, which is why
nextIdcan be passed as it is.
Commit the generated file. Its own header recommends it, and a fresh clone then type-checks before anyone has started the dev server.
Repeating the route name in every page gets old. The Volar plugin types useRoute() and $route in page components from the file path, so a bare useRoute() is enough. Add it, with rootDir, to the tsconfig that includes your Vue files (tsconfig.app.json in a create-vue project):
{
"compilerOptions": {
"rootDir": "."
},
"vueCompilerOptions": {
"plugins": ["vue-router/volar/sfc-typed-router"]
}
}
Merge these keys into your existing file rather than replacing it. The plugin works out each page's path from rootDir. Without it, vue-tsc warns "No rootDir specified" and a bare useRoute() does not get the page's params.
Data loaders, still experimental
Data loaders move fetching out of the component. You define a loader with defineBasicLoader() and export it from a page component. The router runs it during navigation and waits for it before showing the page, and the page reads data, isLoading, error and reload from it. Install the plugin in src/main.ts, before the router, because adding the router starts the first navigation. The router here is the named export from src/router/index.ts above:
import { createApp } from 'vue'
import { DataLoaderPlugin } from 'vue-router/experimental'
import App from './App.vue'
import { router } from './router'
const app = createApp(App)
app.use(DataLoaderPlugin, { router })
app.use(router)
app.mount('#app')
Try data loaders in a side project. In an app you ship, keep fetching inside your components or your data layer until the API leaves the experimental path.
Version 6 is planned to be ESM only
The migration guide states the plan in two sentences: Vue Router 6 will be ESM only and will remove deprecated APIs, and version 5 exists to give you time to prepare. ESM only means the package will stop shipping a CommonJS build, the one loaded with require('vue-router').
A Vite app is already ESM. The places to check are an old test setup, a Node script or a server entry that still uses require. The guide does not yet list which APIs will be removed, so there is nothing more to change today.
Who should upgrade now, and who should wait
- An app with a routes array: upgrade now. It is one command, the guide promises no code changes, and staying on the current major means every fix lands where you are.
- An app on unplugin-vue-router: upgrade now as well. The guide calls the move "mostly import path changes": remove the old dependency, update
vue-router, and point each import at its new path. Change the imports in one commit, runvue-tscand the dev server, and you are done in an afternoon. - A new project: nothing to do. When you pick the router option, create-vue 3.21.0, published on 2 February, installs
vue-router5 and still writes a classic routes array insrc/router/index.ts. File-based routing stays opt-in. - A Nuxt app: leave the router to Nuxt. Nuxt has its own pages integration and chooses the router version itself (Nuxt 4.3 depends on
vue-router^4.6.4), so do not add version 5 by hand. - A page that loads the router from a CDN: wait if you rely on the devtools there, because the IIFE build no longer includes them.