File-based routing with Vue Router 5
Let the files in src/pages define your routes with the plugin built into Vue Router 5, get typed route names and params, and set meta and names from inside a page with definePage().
What you'll learn
When you finish, you can do these.- 1Turn page files into routes with Vue Router 5
- 2Name files for a param, a layout and a 404 page
- 3Catch a wrong route name before the app runs
- 4Protect a page from its own file with definePage()
A small site has a home page, a sign-in page and a page for each user. With Vue Router, Vue's official library for pages, each page exists twice: as a component file, and as an entry in the routes list, the array that pairs each address with its component. Add a page and you edit two places. Rename one and you hunt for every link to it.
File-based routing removes the copy: the files in one folder, src/pages, are the routes, and a plugin built into Vue Router 5 writes the list for you. The Code tab holds that site's files: the plugin in vite.config.ts, the router in src/router.ts and the pages in src/pages. By the end you move two pages of your own project into src/pages. You need one idea from Navigation guards, lesson four of this track: a guard is a function the router runs before each page change. A sign-in guard reads a route's meta, extra facts such as requiresAuth: true, to know which pages need a signed-in user.
Files become routes
Two files set up the plugin, and then the names of your page files decide the addresses.
The first file is vite.config.ts, the settings file of Vite, your build tool. The plugin, imported as VueRouter from vue-router/vite, goes before vue(), the Vue plugin, as the docs ask, so it reads each page before Vue compiles it.
export default defineConfig({
plugins: [
VueRouter({ dts: 'src/route-map.d.ts' }),
vue(),
],
})
The plugin's dts option says where to write a types file, the list of routes that TypeScript checks your links against.
The second file is src/router.ts. It imports routes, the list the plugin generates, from vue-router/auto-routes, and hands it to createRouter(), the function that makes the router, in place of a hand-written list.
import { createRouter, createWebHistory } from 'vue-router'
import { routes, handleHotUpdate } from 'vue-router/auto-routes'
export const router = createRouter({
history: createWebHistory(),
routes,
})
Everything else works as with a hand-written list: RouterLink for links, RouterView where the page shows. handleHotUpdate(router), at the end of the Code tab's file, updates the routes without a full reload when you add or rename a page.
- src/pagesyour page files
- vue-router/vitereads them at build time
- auto-routesthe routes list, plus route-map.d.ts
- createRouterthe router itself, unchanged
Five rules turn a file's path into an address.
index.vueis the address of its folder:src/pages/index.vueis/, andsrc/pages/users/index.vueis/users.- Square brackets make a param, a part of the address that varies:
users/[id].vueis/users/:id, andusers/[id]/edit.vueis/users/:id/edit. - Three dots make a catch-all for any address no other route claims, the place for a 404 page:
[...path].vueis/:path(.*), and the one in the Code tab says Page not found. - A folder in parentheses is a route group. It sorts files without changing addresses, so
(admin)/reports.vueis/reports. - A file next to a folder of the same name is that folder's layout, the frame its pages share. The pages in
users/render inside the<RouterView />ofusers.vue: they become its child routes (Nested routes and layouts).
With the Code tab's files, /users shows the Users heading of users.vue, with the text of users/index.vue under it.
Typed names and definePage()
File-based routes get names that TypeScript checks, and each page can set its own route options.
Every generated route is named after its file: '/', '/users', '/users/[id]'. The home page, src/pages/index.vue, links to user 1 with name: '/users/[id]' and params: { id: '1' }.
The plugin's types file, src/route-map.d.ts, lists every name with its path and params, so TypeScript checks every link: a typo such as name: '/user/[id]' fails vue-tsc, the command that type-checks a Vue project, with a message that ends in Did you mean '"/users/[id]"'?.
Params are typed too. The user page, src/pages/users/[id].vue, reads the current route with useRoute('/users/[id]'), and TypeScript knows that route.params.id exists. The plugin rewrites the types file when pages change, so commit it with your code.
A page sets its own route options, such as meta, with definePage(). Like defineProps(), which declares a component's props, it is a compiler macro: the build handles it, so it needs no import, while useRoute still does. The user page asks for a signed-in visitor:
<script setup lang="ts">
import { useRoute } from 'vue-router'
definePage({
meta: { requiresAuth: true },
})
const route = useRoute('/users/[id]')
</script>
At build time the plugin lifts this object out of the component into the generated routes list. That is why it cannot use the component's variables.
The Code tab's src/router.ts also holds a sign-in guard, like the one in Navigation guards, that reads to.meta.requiresAuth, so a signed-out visitor who opens /users/1 lands on the Sign in page at /login.
What goes wrong
Two mistakes leave a wrong route name in your app: a types file TypeScript cannot see, and a rename.
The first is a types file outside include, the list of files TypeScript checks, in its settings file tsconfig.app.json. With no dts option, the plugin writes typed-router.d.ts at the project root:
VueRouter(), // writes typed-router.d.ts at the project root
A project made with create-vue, Vue's official project starter, checks src/**/*, every file inside src/. TypeScript never reads the file at the root, so route names become plain strings and vue-tsc passes every typo. Write the file inside src/, or add it to include:
VueRouter({ dts: 'src/route-map.d.ts' }),
Restart your editor's TypeScript server, and a mistyped name fails again.
The second is a rename. Names and params come from file names, so renaming users/[id].vue to users/[userId].vue changes both, and the home page's link still uses the old ones:
<!-- users/[id].vue was renamed to users/[userId].vue -->
<RouterLink :to="{ name: '/users/[id]', params: { id: '1' } }">User 1</RouterLink>
The route is now '/users/[userId]' with a userId param. vue-tsc fails on this link and on every route.params.id, which is the point: it lists each place to fix. Without type checking, the link throws when it renders, with an error that starts with No match for.
<RouterLink :to="{ name: '/users/[userId]', params: { userId: '1' } }">User 1</RouterLink>
Once every place uses the new name and param, vue-tsc passes and the link works again. To keep a name when a file moves, set it with definePage({ name: 'user' }). Params still follow the file name.
What to remember
- Vue Router 5 ships file-based routing.
vue-router/vitereadssrc/pages, androutesfromvue-router/auto-routesgo intocreateRouter(). index.vueis a folder's own address,[id]is a param,[...path]catches everything else, a(group)folder leaves the address alone, andusers.vuenext tousers/is their layout.- Route names are file paths, typed through the generated
route-map.d.ts, which must sit inside your tsconfiginclude. - Moving or renaming a page changes its name and params. Let
vue-tscfind every link that needs updating. definePage()sets meta from inside a page, and a guard reads it fromto.meta.
In your project
Do these in a real project. Each one says how to confirm it worked.- Convert two routes in a project of yours. Move its home and about pages to
src/pages/index.vueandsrc/pages/about.vue, add the plugin as invite.config.ts, and replace the hand-written list withroutesfromvue-router/auto-routes. Startnpm run devand printrouter.getRoutes().map(r => r.path), the path of every route, in the console: you see/and/about, andsrc/route-map.d.tsnow exists. - Mistype a route name in a RouterLink, such as
name: '/abuot', and runnpm run type-check(vue-tsc --buildin a create-vue project). It fails, and the message ends withDid you mean '"/about"'?. Fix the name and it passes. - Add
definePage({ meta: { requiresAuth: true } })toabout.vue, and a sign-in guard like the one in the Code tab'ssrc/router.tsto yourrouter.ts. While signed out, open/about: the address changes to your sign-in page, with no change to the routes list.