---
title: "Vue Router 5: file-based routing moves into core · vuejs.school"
description: "Vue Router 5 merges unplugin-vue-router into the main package. Apps with a routes array upgrade with one command; file-based apps change a few imports."
canonical_url: "https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core"
meta:
  "og:description": "Vue Router 5 merges unplugin-vue-router into the main package. Apps with a routes array upgrade with one command; file-based apps change a few imports."
  "og:image": "https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3JlIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3Jl%2Cdescription_~VnVlIFJvdXRlciA1IG1lcmdlcyB1bnBsdWdpbi12dWUtcm91dGVyIGludG8gdGhlIG1haW4gcGFja2FnZS4gQXBwcyB3aXRoIGEgcm91dGVzIGFycmF5IHVwZ3JhZGUgd2l0aCBvbmUgY29tbWFuZDsgZmlsZS1iYXNlZCBhcHBzIGNoYW5nZSBhIGZldyBpbXBvcnRzLg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTUtZmlsZS1iYXNlZC1yb3V0aW5nLW1vdmVzLWludG8tY29yZSI%2Cs_H86ZUPqePE-B-z_J.png"
  "og:image:alt": "Vue Router 5: file-based routing moves into core · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Vue Router 5: file-based routing moves into core · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3JlIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3Jl%2Cdescription_~VnVlIFJvdXRlciA1IG1lcmdlcyB1bnBsdWdpbi12dWUtcm91dGVyIGludG8gdGhlIG1haW4gcGFja2FnZS4gQXBwcyB3aXRoIGEgcm91dGVzIGFycmF5IHVwZ3JhZGUgd2l0aCBvbmUgY29tbWFuZDsgZmlsZS1iYXNlZCBhcHBzIGNoYW5nZSBhIGZldyBpbXBvcnRzLg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTUtZmlsZS1iYXNlZC1yb3V0aW5nLW1vdmVzLWludG8tY29yZSI%2Cs_H86ZUPqePE-B-z_J.png"
  "twitter:image:alt": "Vue Router 5: file-based routing moves into core · vuejs.school"
---

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

## What shipped

The release notes and the [migration guide](https://router.vuejs.org/guide/migration/v4-to-v5.html) list these changes:

- unplugin-vue-router is merged into `vue-router`. The Vite plugin is imported from `vue-router/vite`, the generated routes from `vue-router/auto-routes`, and the plugins for webpack, Rollup and esbuild, with their helper functions, from `vue-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 under `vue-router/experimental/pinia-colada`.
- The Volar plugins move into the package as `vue-router/volar/sfc-typed-router` and `vue-router/volar/sfc-route-blocks`. Volar is the engine behind the Vue editor extension and `vue-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:

*vite.config.ts*

```
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.vue` renders at `/`.
- `src/pages/about.vue` renders at `/about`.
- `src/pages/users/index.vue` renders at `/users`.
- `src/pages/users/[id].vue` renders at `/users/:id`, and `id` becomes a route param.
- `src/pages/[...path].vue` matches every URL nothing else matched, which is where a 404 page goes.

The router file stops listing routes and imports them instead:

*src/router/index.ts*

```
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](https://uvr.esm.is/guide/file-based-routing). 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:

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

```
<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.id` is a `string`, and reading any other param, such as `route.params.slug`, is an error.
- The `RouterLink`'s `to` is checked against the same map, so a typo such as `name: '/user/[id]'` fails `vue-tsc` instead of failing in the browser.
- Params you pass in accept numbers as well as strings, which is why `nextId` can 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):

*tsconfig.app.json*

```
{
  "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:

*src/main.ts*

```
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, run `vue-tsc` and 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-router` 5 and still writes a classic routes array in `src/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.

**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) [Tutorial · 5 min read**Migrating from unplugin-vue-router to Vue Router 5**](https://vuejs.school/community/blog/migrating-from-unplugin-vue-router-to-vue-router-5)

```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/vue-router-5-file-based-routing-moves-into-core#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-02-03","description":"Vue Router 5 merges unplugin-vue-router into the main package. Apps with a routes array upgrade with one command; file-based apps change a few imports.","name":"Vue Router 5: file-based routing moves into core","url":"https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core","breadcrumb":{"@id":"https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core"]}],"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/vue-router-5-file-based-routing-moves-into-core#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-02-03","description":"Vue Router 5 merges unplugin-vue-router into the main package. Apps with a routes array upgrade with one command; file-based apps change a few imports.","headline":"Vue Router 5: file-based routing moves into core","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3JlIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3Jl%2Cdescription_~VnVlIFJvdXRlciA1IG1lcmdlcyB1bnBsdWdpbi12dWUtcm91dGVyIGludG8gdGhlIG1haW4gcGFja2FnZS4gQXBwcyB3aXRoIGEgcm91dGVzIGFycmF5IHVwZ3JhZGUgd2l0aCBvbmUgY29tbWFuZDsgZmlsZS1iYXNlZCBhcHBzIGNoYW5nZSBhIGZldyBpbXBvcnRzLg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTUtZmlsZS1iYXNlZC1yb3V0aW5nLW1vdmVzLWludG8tY29yZSI%2Cs_H86ZUPqePE-B-z_J.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vue-router-5-file-based-routing-moves-into-core#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Vue Router 5: file-based routing moves into core","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_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3JlIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3Jl%2Cdescription_~VnVlIFJvdXRlciA1IG1lcmdlcyB1bnBsdWdpbi12dWUtcm91dGVyIGludG8gdGhlIG1haW4gcGFja2FnZS4gQXBwcyB3aXRoIGEgcm91dGVzIGFycmF5IHVwZ3JhZGUgd2l0aCBvbmUgY29tbWFuZDsgZmlsZS1iYXNlZCBhcHBzIGNoYW5nZSBhIGZldyBpbXBvcnRzLg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTUtZmlsZS1iYXNlZC1yb3V0aW5nLW1vdmVzLWludG8tY29yZSI%2Cs_H86ZUPqePE-B-z_J.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3JlIMK3IHZ1ZWpzLnNjaG9vbA%2Cc_OgCard.takumi%2Ctitle_~VnVlIFJvdXRlciA1OiBmaWxlLWJhc2VkIHJvdXRpbmcgbW92ZXMgaW50byBjb3Jl%2Cdescription_~VnVlIFJvdXRlciA1IG1lcmdlcyB1bnBsdWdpbi12dWUtcm91dGVyIGludG8gdGhlIG1haW4gcGFja2FnZS4gQXBwcyB3aXRoIGEgcm91dGVzIGFycmF5IHVwZ3JhZGUgd2l0aCBvbmUgY29tbWFuZDsgZmlsZS1iYXNlZCBhcHBzIGNoYW5nZSBhIGZldyBpbXBvcnRzLg%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_peach%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtcm91dGVyLTUtZmlsZS1iYXNlZC1yb3V0aW5nLW1vdmVzLWludG8tY29yZSI%2Cs_H86ZUPqePE-B-z_J.png"}]}
```