---
title: "Migrating from unplugin-vue-router to Vue Router 5 · vuejs.school"
description: "A step-by-step move from unplugin-vue-router to Vue Router 5, one diff per step, plus the error messages you may see afterwards and how to fix each one."
canonical_url: "https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5"
meta:
  "og:description": "A step-by-step move from unplugin-vue-router to Vue Router 5, one diff per step, plus the error messages you may see afterwards and how to fix each one."
  "og:image": "https://vuejs.school/_og/d/a_~TWlncmF0aW5nIGZyb20gdW5wbHVnaW4tdnVlLXJvdXRlciB0byBWdWUgUm91dGVyIDUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Migrating%2Bfrom%2Bunplugin-vue-router%2Bto%2BVue%2BRouter%2B5%2Cdescription_~QSBzdGVwLWJ5LXN0ZXAgbW92ZSBmcm9tIHVucGx1Z2luLXZ1ZS1yb3V0ZXIgdG8gVnVlIFJvdXRlciA1LCBvbmUgZGlmZiBwZXIgc3RlcCwgcGx1cyB0aGUgZXJyb3IgbWVzc2FnZXMgeW91IG1heSBzZWUgYWZ0ZXJ3YXJkcyBhbmQgaG93IHRvIGZpeCBlYWNoIG9uZS4%2Ceyebrow_~VHV0b3JpYWwgwrcgNSBtaW4gcmVhZA%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy9taWdyYXRpbmctZnJvbS11bnBsdWdpbi12dWUtcm91dGVyLXRvLXZ1ZS1yb3V0ZXItNSI%2Cs_O8kFjZvPv5sxYDL1.png"
  "og:image:alt": "Migrating from unplugin-vue-router to Vue Router 5 · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Migrating from unplugin-vue-router to Vue Router 5 · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~TWlncmF0aW5nIGZyb20gdW5wbHVnaW4tdnVlLXJvdXRlciB0byBWdWUgUm91dGVyIDUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Migrating%2Bfrom%2Bunplugin-vue-router%2Bto%2BVue%2BRouter%2B5%2Cdescription_~QSBzdGVwLWJ5LXN0ZXAgbW92ZSBmcm9tIHVucGx1Z2luLXZ1ZS1yb3V0ZXIgdG8gVnVlIFJvdXRlciA1LCBvbmUgZGlmZiBwZXIgc3RlcCwgcGx1cyB0aGUgZXJyb3IgbWVzc2FnZXMgeW91IG1heSBzZWUgYWZ0ZXJ3YXJkcyBhbmQgaG93IHRvIGZpeCBlYWNoIG9uZS4%2Ceyebrow_~VHV0b3JpYWwgwrcgNSBtaW4gcmVhZA%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy9taWdyYXRpbmctZnJvbS11bnBsdWdpbi12dWUtcm91dGVyLXRvLXZ1ZS1yb3V0ZXItNSI%2Cs_O8kFjZvPv5sxYDL1.png"
  "twitter:image:alt": "Migrating from unplugin-vue-router to Vue Router 5 · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## Step 1: swap the dependencies

Start from a clean Git working tree, so every change shows up in one diff, and check that the app builds. If the build was already failing, you will not know which errors are yours. Then remove the plugin and move the router to the new major:

```
pnpm remove unplugin-vue-router
pnpm update vue-router@5
```

With npm, run `npm uninstall unplugin-vue-router` and then `npm install vue-router@5`. Either way you get 5.0.2, the current patch, which includes the 5.0.1 fix that makes the typed-router Volar plugin work with `vue-tsc` (step 4 uses it). Vue itself does not need to move: Vue Router 5 asks for Vue 3.5 or newer, the same as 4.6.

If you also use data loaders with Pinia Colada, check that version too. The 5.0.2 package declares `@pinia/colada` 0.21.2 or newer as an optional peer dependency, so your package manager reports a peer dependency conflict if yours is older.

Nothing will build right now. That is expected: the config still imports a package that is gone.

## Step 2: change the plugin import

The Vite plugin now comes from `vue-router/vite`. While you are in the file, add the `dts` option the guide recommends, which moves the generated types into `src/`:

*vite.config.ts**Breaks***

```
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueRouter from 'unplugin-vue-router/vite'

export default defineConfig({
  plugins: [VueRouter(), vue()],
})
```

*vite.config.ts**Works***

```
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',
    }),
    vue(),
  ],
})
```

Keep `VueRouter()` before `vue()`, as before. Any other options you passed, such as `routesFolder` or `extensions`, stay exactly as they were. The `Options` type you may have imported to type them now comes from `vue-router/unplugin`.

Projects built with webpack, Rollup or esbuild import from `vue-router/unplugin` instead and call `VueRouter.webpack()`, `VueRouter.rollup()` and so on. The same entry exports the helpers that custom integrations use: `VueRouterAutoImports`, `EditableTreeNode`, `createTreeNodeValue`, `createRoutesContext`, `getFileBasedRouteName` and `getPascalCaseRouteName`. If you feed `VueRouterAutoImports` to unplugin-auto-import, change only that import line.

## Step 3: move the generated types

unplugin-vue-router wrote its route map to `typed-router.d.ts` at the project root, and its setup had you include that file in `tsconfig.json` and add a reference to `unplugin-vue-router/client`. Remove both. In a create-vue project the reference sits in `env.d.ts`:

*env.d.ts**Breaks***

```
/// <reference types="vite/client" />
/// <reference types="unplugin-vue-router/client" />
```

*env.d.ts**Works***

```
/// <reference types="vite/client" />
```

If you put the types in `tsconfig.json` instead, remove `"./typed-router.d.ts"` from `include`, and `"unplugin-vue-router/client"` from wherever you listed it. With `dts: 'src/route-map.d.ts'` you add nothing back: create-vue's `tsconfig.app.json` already includes `src/**/*`, which is the reason the guide suggests that path. If your tsconfig does not include `src`, add the new file to its `include`.

Then start the dev server once, so the plugin writes `src/route-map.d.ts`, and delete the old `typed-router.d.ts`. Nothing regenerates it any more, so it would only go stale. Commit the new file in its place.

## Step 4: rename the Volar plugins

Skip this step if no tsconfig in your project lists a plugin starting with `unplugin-vue-router/volar/`. Volar is the language-tools framework behind the Vue editor extension and `vue-tsc`, and unplugin-vue-router shipped two plugins for it: `sfc-typed-router` types `useRoute()` in a page from the page's file path, and `sfc-route-blocks` gives `<route>` blocks editor support. Change their prefix in the tsconfig that includes your Vue files:

*tsconfig.app.json*

```
{
  "compilerOptions": {
    "rootDir": "."
  },
  "vueCompilerOptions": {
    "plugins": [
      "vue-router/volar/sfc-typed-router",
      "vue-router/volar/sfc-route-blocks"
    ]
  }
}
```

Before, both strings started with `unplugin-vue-router/volar/`. Keep `"rootDir": "."`, which the plugins need to resolve paths, and merge these keys into your file rather than replacing it.

## Step 5: update the data loader imports

Data loaders load a page's data inside a navigation guard, so the data is ready when the page renders. They were experimental in the plugin, they are still experimental, and their new address says so. Three old paths collapse into two. In `src/main.ts` only the import changes; `app.use(DataLoaderPlugin, { router })` stays as it was:

*src/main.ts*

```
// before: import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders'
import { DataLoaderPlugin } from 'vue-router/experimental'
```

In a page, the loader sits in a plain `<script>` block next to `<script setup>`, because the router collects a page's loaders from its exports, and `<script setup>` cannot contain exports. Again, only the import line changes:

*src/pages/users/\[id].vue*

```
<script lang="ts">
// before: import { defineBasicLoader } from 'unplugin-vue-router/data-loaders/basic'
import { defineBasicLoader } from 'vue-router/experimental'

export const useUser = defineBasicLoader('/users/[id]', async (route) => {
  const res = await fetch(`/api/users/${route.params.id}`)
  return (await res.json()) as { id: string; name: string }
})
</script>

<script setup lang="ts">
const { data: user, isLoading, error } = useUser()
</script>

<template>
  <p v-if="isLoading">Loading…</p>
  <p v-else-if="error">{{ error.message }}</p>
  <h1 v-else-if="user">{{ user.name }}</h1>
</template>
```

`defineColadaLoader` moves from `unplugin-vue-router/data-loaders/pinia-colada` to `vue-router/experimental/pinia-colada`.

## What stays the same

Your router file imports `routes` (and `handleHotUpdate`, if you set up hot reloading) from `vue-router/auto-routes`. unplugin-vue-router has used that module name since version 0.8, when it replaced `vue-router/auto/routes`, and Vue Router 5 keeps it: the package now carries the module's types, and its Vite plugin generates the routes behind it, as the old plugin did. The line stays as it is.

Page files do not move or change. Route names, which are file paths such as `/users/[id]`, are generated the same way, so every `useRoute('/users/[id]')` and every named `RouterLink` keeps its types.

`definePage()` needs no import. It is a compiler macro, and in 5.0 the types behind `vue-router/auto-routes` declare it as a global. The guide does not mention this case, but if a file imports it explicitly from `unplugin-vue-router/runtime`, delete that import line.

## Errors after migrating, and the fix for each

Run the dev server, then `vue-tsc --build` (create-vue's `type-check` script). If a step was missed, you will see one of these.

`failed to load config from vite.config.ts`, followed by `Cannot find package 'unplugin-vue-router'`. The config still imports the old plugin. Go back to step 2.

`Cannot find type definition file for 'unplugin-vue-router/client'`. A reference survived in `env.d.ts`, or in the `types` array of a tsconfig. Delete it.

`Failed to resolve import "unplugin-vue-router/data-loaders/basic" from "src/pages/users/[id].vue". Does the file exist?` A page still imports a loader from the old path. Search the `src/pages` folder for it and apply step 5.

Route types stop working: `useRoute('/users/[id]')` complains, or `route.params` falls back to a loose type. The guide's advice is to restart the TypeScript server in your editor and check that the generated file, `src/route-map.d.ts`, is included by your tsconfig.

The routes array is empty and every URL renders nothing. The plugin is not finding pages. Check `routesFolder` and the file extensions you allow; the default is `src/pages` with `.vue` files.

A route name is reported as unknown. Use the generated name, or give the page a name of your own with `definePage({ name: 'user-profile' })` and use that.

ESLint reports `'definePage' is not defined` in a JavaScript project. The `no-undef` rule does not know about compiler macros. The [unplugin-vue-router docs](https://uvr.esm.is/guide/eslint) give the fix, and it still applies: declare `definePage` as a `readonly` global in your ESLint config. TypeScript projects rarely see this, because the TypeScript presets switch `no-undef` off.

**Check it stuck**3 questions from the File-based routing with Vue Router 5 quiz.

[**Practice now**](https://vuejs.school/learn/routing/file-based-routing-with-vue-router-5?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Tutorial · 6 min read**Dynamic routes, params and typed routes**](https://vuejs.school/community/blog/dynamic-routes-params-and-typed-routes) [Release notes · 7 min read**Vue Router 5: file-based routing moves into core**](https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-02-05","description":"A step-by-step move from unplugin-vue-router to Vue Router 5, one diff per step, plus the error messages you may see afterwards and how to fix each one.","name":"Migrating from unplugin-vue-router to Vue Router 5","url":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5","breadcrumb":{"@id":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-02-05","description":"A step-by-step move from unplugin-vue-router to Vue Router 5, one diff per step, plus the error messages you may see afterwards and how to fix each one.","headline":"Migrating from unplugin-vue-router to Vue Router 5","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~TWlncmF0aW5nIGZyb20gdW5wbHVnaW4tdnVlLXJvdXRlciB0byBWdWUgUm91dGVyIDUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Migrating%2Bfrom%2Bunplugin-vue-router%2Bto%2BVue%2BRouter%2B5%2Cdescription_~QSBzdGVwLWJ5LXN0ZXAgbW92ZSBmcm9tIHVucGx1Z2luLXZ1ZS1yb3V0ZXIgdG8gVnVlIFJvdXRlciA1LCBvbmUgZGlmZiBwZXIgc3RlcCwgcGx1cyB0aGUgZXJyb3IgbWVzc2FnZXMgeW91IG1heSBzZWUgYWZ0ZXJ3YXJkcyBhbmQgaG93IHRvIGZpeCBlYWNoIG9uZS4%2Ceyebrow_~VHV0b3JpYWwgwrcgNSBtaW4gcmVhZA%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy9taWdyYXRpbmctZnJvbS11bnBsdWdpbi12dWUtcm91dGVyLXRvLXZ1ZS1yb3V0ZXItNSI%2Cs_O8kFjZvPv5sxYDL1.png","@type":["Article","BlogPosting"],"articleSection":["Tutorial"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Migrating from unplugin-vue-router to Vue Router 5","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~TWlncmF0aW5nIGZyb20gdW5wbHVnaW4tdnVlLXJvdXRlciB0byBWdWUgUm91dGVyIDUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Migrating%2Bfrom%2Bunplugin-vue-router%2Bto%2BVue%2BRouter%2B5%2Cdescription_~QSBzdGVwLWJ5LXN0ZXAgbW92ZSBmcm9tIHVucGx1Z2luLXZ1ZS1yb3V0ZXIgdG8gVnVlIFJvdXRlciA1LCBvbmUgZGlmZiBwZXIgc3RlcCwgcGx1cyB0aGUgZXJyb3IgbWVzc2FnZXMgeW91IG1heSBzZWUgYWZ0ZXJ3YXJkcyBhbmQgaG93IHRvIGZpeCBlYWNoIG9uZS4%2Ceyebrow_~VHV0b3JpYWwgwrcgNSBtaW4gcmVhZA%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy9taWdyYXRpbmctZnJvbS11bnBsdWdpbi12dWUtcm91dGVyLXRvLXZ1ZS1yb3V0ZXItNSI%2Cs_O8kFjZvPv5sxYDL1.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~TWlncmF0aW5nIGZyb20gdW5wbHVnaW4tdnVlLXJvdXRlciB0byBWdWUgUm91dGVyIDUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_Migrating%2Bfrom%2Bunplugin-vue-router%2Bto%2BVue%2BRouter%2B5%2Cdescription_~QSBzdGVwLWJ5LXN0ZXAgbW92ZSBmcm9tIHVucGx1Z2luLXZ1ZS1yb3V0ZXIgdG8gVnVlIFJvdXRlciA1LCBvbmUgZGlmZiBwZXIgc3RlcCwgcGx1cyB0aGUgZXJyb3IgbWVzc2FnZXMgeW91IG1heSBzZWUgYWZ0ZXJ3YXJkcyBhbmQgaG93IHRvIGZpeCBlYWNoIG9uZS4%2Ceyebrow_~VHV0b3JpYWwgwrcgNSBtaW4gcmVhZA%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy9taWdyYXRpbmctZnJvbS11bnBsdWdpbi12dWUtcm91dGVyLXRvLXZ1ZS1yb3V0ZXItNSI%2Cs_O8kFjZvPv5sxYDL1.png"}]}
```