--- url: /data-loaders/basic.md --- # `defineBasicLoader()` Basic data loader that always reruns on navigation. ::: warning Data Loaders are experimental. Feedback is very welcome to shape the future of data loaders in Vue Router. ::: ## Setup ## Example ```vue ``` ## SSR ## Nuxt ## Unresolved Questions * Should this basic version also track what is used in the route object, like [Svelte Data Loaders do](https://kit.svelte.dev/docs/load#rerunning-load-functions)? --- --- url: /data-loaders/colada.md --- # `defineColadaLoader()` Loaders that use [@pinia/colada](https://github.com/posva/pinia-colada) under the hood. These loaders provide the most efficient solution to asynchronous state with cache, ssr support and more. The key used in these loaders is directly passed to `useQuery()` from `@pinia/colada` and can be invalidated with `useMutation()` hooks. See [Pinia Colada documentation about query invalidation](https://pinia-colada.esm.dev/guide/query-invalidation.html) to learn more. ## Setup Follow the installation instructions in [@pinia/colada](https://github.com/posva/pinia-colada). ## Example ```vue ``` ::: tip You can pass a route name to `defineColadaLoader` to get typed routes in the `query` function. ```ts export const useUserData = defineColadaLoader('/users/[id]', { // ... }) ``` ::: ## Refresh by default To avoid unnecessary frequent refreshes, Pinia Colada refreshes the data when navigating (instead of *refetching*). Change the `staleTime` option to control how often the data should be fetched, e.g. setting it to 0 will fetch the data every time the route changes. ## Route tracking The `query` function tracks what is used in the `to` parameter and will only refresh the data if **tracked** properties change. This means that if you use `to.params.id` in the `query` function, it will only refetch the data if the `id` parameter changes but not if other properties like `to.query`, `to.hash` or even `to.params.other` change. To make sure the data is updated, it will still refresh in these scenarios. Configure the `staleTime` option to control how often the data should be refreshed. ## SSR Follow Pinia Colada docs for SSR. ## Nuxt Use `@pinia/colada-nuxt` plugin and SSR will work out of the box. Hydration won't trigger an extra fetch! --- --- url: /guide/essentials/active-links.md --- # Active links It's common for applications to have a navigation component that renders a list of RouterLink components. Within that list, we might want to style links to the currently active route differently from the others. The RouterLink component adds two CSS classes to active links, `router-link-active` and `router-link-exact-active`. To understand the difference between them, we first need to consider how Vue Router decides that a link is *active*. ## When are links active? A RouterLink is considered to be ***active*** if: 1. It matches the same route record (i.e. configured route) as the current location. 2. It has the same values for the `params` as the current location. If you're using [nested routes](./nested-routes), any links to ancestor routes will also be considered active if the relevant `params` match. Other route properties, such as the [`query`](../../api/interfaces/RouteLocationBase.html#query), are not taken into account. The path doesn't necessarily need to be a perfect match. For example, using an [`alias`](./redirect-and-alias#Alias) would still be considered a match, so long as it resolves to the same route record and `params`. If a route has a [`redirect`](./redirect-and-alias#Redirect), it won't be followed when checking whether a link is active. ## Exact active links An ***exact*** match does not include ancestor routes. Let's imagine we have the following routes: ```js const routes = [ { path: '/user/:username', component: User, children: [ { path: 'role/:roleId', component: Role, }, ], }, ] ``` Then consider these two links: ```vue-html User Role ``` If the current location path is `/user/erina/role/admin` then these would both be considered *active*, so the class `router-link-active` would be applied to both links. But only the second link would be considered *exact*, so only that second link would have the class `router-link-exact-active`. ## Configuring the classes The RouterLink component has two props, `activeClass` and `exactActiveClass`, that can be used to change the names of the classes that are applied: ```vue-html ``` The default class names can also be changed globally by passing the `linkActiveClass` and `linkExactActiveClass` options to `createRouter()`: ```js const router = createRouter({ linkActiveClass: 'border-indigo-500', linkExactActiveClass: 'border-indigo-700', // ... }) ``` See [Extending RouterLink](../advanced/extending-router-link) for more advanced customization techniques using the `v-slot` API. --- --- url: /data-loaders/load-cancellation.md --- # Cancelling a data loader Data loaders receive an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can be passed to `fetch` and other Web APIs to cancel ongoing requests when the navigation is cancelled. If the navigation is cancelled because of errors or a new navigation, the signal aborts, causing any request using it to abort as well. ```ts twoslash interface Book { title: string isbn: string description: string } function fetchBookCollection(options: { signal?: AbortSignal }): Promise { return {} as any } // ---cut--- import { defineBasicLoader } from 'vue-router/experimental' export const useBookCollection = defineBasicLoader( async (_route, { signal }) => { return fetchBookCollection({ signal }) } ) ``` This aligns with the future [Navigation API](https://github.com/WICG/navigation-api#navigation-monitoring-and-interception) and other web APIs that use the `AbortSignal` to cancel an ongoing invocation. ## Best practices Depending on the data loader implementation, it might be more interesting **not** to cancel an ongoing request, for example, when using [Pinia Colada](./colada/), it might be more interesting to keep the request ongoing and cache the result for future navigations. Make sure to read the documentation --- --- url: /file-based-routing/configuration.md --- # Configuration Have a glimpse of all the existing configuration options with their corresponding **default values**: ```ts import VueRouter from 'vue-router/vite' VueRouter({ // how and what folders to scan for files routesFolder: [ { src: 'src/pages', path: '', // override globals exclude: excluded => excluded, filePatterns: filePatterns => filePatterns, extensions: extensions => extensions, }, ], // what files should be considered as a pages extensions: ['.vue'], // what files to include filePatterns: ['**/*'], // files to exclude from the scan exclude: [], // where to generate the types dts: './typed-router.d.ts', // how to generate the route name getRouteName: routeNode => getFileBasedRouteName(routeNode), // default language for custom blocks routeBlockLang: 'json5', // how to import routes, can also be a string importMode: 'async', // where are paths relative to root: process.cwd(), // options for the path parser pathParser: { // should `users.[id]` be parsed as `users/:id`? dotNesting: true, }, // modify routes individually async extendRoute(route) { // ... }, // modify routes before writing async beforeWriteFiles(rootRoute) { // ... }, }) ``` ## SSR It might be necessary to mark `vue-router` as `noExternal` in your `vite.config.js` in development mode: ```ts{7} import { defineConfig } from 'vite' import Vue from '@vitejs/plugin-vue' import VueRouter from 'vue-router/vite' export default defineConfig(({ mode }) => ({ ssr: { noExternal: mode === 'development' ? ['vue-router'] : [], }, plugins: [VueRouter(), Vue()], })) ``` --- --- url: /experimental/param-parsers.md --- # Custom Param Parsers ::: warning Experimental This feature is part of the [Experimental Router](./router-resolver.md). API and ergonomics may change. Make sure you've set it up first. ::: Param parsers transform raw URL strings into rich JS values (and back) for both **path** and **query** params, with end-to-end TypeScript types. \[\[toc]] ## The problem (current way) In the stable router, params and query come in as `string | string[] | null`. You either: * pin a regex inline: `path: '/users/:id(\\d+)'`. Still typed as `string`, no parsing. * coerce by hand inside the component: `const id = Number(route.params.id)`. * write a `beforeEach` guard to validate or redirect: cannot let other routes match. This works but the type system can't help you, every consumer has to know the convention, and query params are even worse. ## Setup Enable `experimental.paramParsers` in the Vue Router Vite plugin. This tells the plugin where to scan for custom parsers and registers them both at runtime and in the generated `typed-router.d.ts`. ```ts [vite.config.ts] import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import VueRouter from 'vue-router/vite' export default defineConfig({ plugins: [ VueRouter({ experimental: { paramParsers: { dir: 'src/params', }, }, }), vue(), ], }) ``` `src/params` is the default directory when setting `paramParsers` to `true`, but you can point `dir` to any project-relative folder, or to an array of folders. ## Built-in parsers | Name | Path | Query | Type | | -------- | :--: | :---: | --------- | | `int` | ✅ | ✅ | `number` | | `bool` | ✅ | ✅ | `boolean` | | `string` | ✅ | ✅ | `string` | `string` is the default param parser and does nothing. It's equivalent to not setting the parser. ```vue ``` ## Defining custom parsers You define param parsers as modules exporting a `parser` in the configured param parser directory. The file name is the parser name you use in routes. For example, `src/params/uuid.ts` exports a `parser` that validates UUIDs and can be used as `[id=uuid]` in route files. A parser is just an object with a *getter* and a *setter* but to make things simpler to use, Vue Router provides two helpers: [`defineParamParser()`](#defineParamParser) and [`defineParamParserRaw()`](#defineParamParserRaw). Reach for `defineParamParser` first, it's the most common use case for simple one-to-one transforms. Use `defineParamParserRaw` when you need to collapse multiple input shapes into one output type or you want to reject *nullish* or array values outright. ### `defineParamParser` `defineParamParser` defines a single-value transform. The router wraps it for optional/repeatable usage and handles `null`/arrays for you. ```ts // src/params/number.ts import { defineParamParser, miss } from 'vue-router/experimental' // pass the final type as a generic to enforce the return type of `get` // and the input type of `set` export const parser = defineParamParser({ get: value => { const n = Number(value) if (Number.isNaN(n)) miss(`"${value}" is not a number`) return n }, set: value => String(value), }) ``` ::: tip Only write validation logic in `get`. The router runs it after `set` to normalize params and the throw will make the `push()`/`resolve()` call fail. ::: This gives us the possibility to transform a param to a number (including floats), while preserving the *shape* of the original params: * `/products/[productId=number].vue` one single param: * `/products/42` → `route.params.productId`: `42` * `/products/[productIds=number]+.vue` repeatable parameter: * `/products/42` → `route.params.productIds`: `[42]` * `/products/42/24` → `route.params.productIds`: `[42, 24]` * `/products/[[productId=number]].vue` one single optional param: * `/products/42` → `route.params.productId`: `42` * `/products` → `route.params.productId`: `null` The logic of the param parser is simple because `defineParamParser()` handles the underlying transformation between single/array/nullish values. You just define how to get from a single string to your desired type and back. ### `defineParamParserRaw` `defineParamParserRaw` gives full control over the transformation. You must handle every shape (`null`, `undefined`, single, array) yourself, but in exchange you can collapse them all into one output type (e.g. always return a `Set`, whether the input was missing, a single value, or an array). Below, the result is always a `Set`, regardless of whether the URL provided nothing, one value, or many. ```ts // src/params/test-set.ts import { defineParamParserRaw } from 'vue-router/experimental' // pass the final type as a generic so `route.params.` is typed export const parser = defineParamParserRaw>({ get: value => { if (value == null) return new Set() return new Set( Array.isArray(value) ? value.filter(v => v != null) : [value] ) }, set: value => [...value], }) ``` ::: tip While you can also return `null`, `undefined`, or a simple *string* from `set`, returning an **array** is usually the best choice: an empty array `[]` is treated the same as `null` (the param is omitted), so a single `[...value]` covers every case without branching. After navigation, `get` runs again to validate the value, so any invalid combination still goes through your own check. ```ts export const parser = defineParamParserRaw>({ get: value => { if (value == null) return new Set() return new Set( Array.isArray(value) ? value.filter(v => v != null) : [value] ) }, // empty Set → [] → param omitted, single → ['one'], many → ['a', 'b'] set: value => [...value], }) ``` Here is a table of the different meaningful combinations of return values from `set` and how the router treats them for path and query params: | `set` returns | Path param | Query param | | -------------------- | ------------------------- | ---------------------------------------------------------------- | | `null` / `undefined` | param is omitted | param is omitted (`undefined`) or rendered empty (`null`, `?k=`) | | `string` | single segment (`/value`) | single entry (`?k=value`) | | `string[]` | repeatable (`/a/b/c`) | repeated entries (`?k=a&k=b`) | ::: ## Errors Throw any error from `get` to mark the value as not matching. The router skips the route (treat it like a 404 candidate). `miss(reason?)` is just sugar for throwing a typed error. ## Standard Schema (Zod / Valibot) Any [Standard Schema](https://standardschema.dev) compatible schema can be used directly as a parser: ```ts // src/params/month-zod.ts import { z } from 'zod' export const parser = z.coerce.number().int().min(1).max(12) ``` ::: warning Standard Schema is one-way: it parses input but cannot serialize back. The router stringifies the value with `String(value)` when navigating, so this only works when `String(parsed) === original`. See [standard-schema#14](https://github.com/standard-schema/standard-schema/issues/14). For anything more complex, use `defineParamParser` with an explicit `set`. ::: ## Using parsers in routes ### Path params You can either *rename your file* to include `=parser` within a *param segment*: `[productId]` -> `[productId=uuid]`, or you can declare the parser through `definePage` without renaming the file: ```vue ``` ### Query params Declared inside `definePage()`: ```vue ``` Options per query field: * `parser`: parser name (from `src/params/*`). Omit for raw string. * `format`: `'value'` (single, takes the **first** value if the URL has several) or `'array'`. * `default`: value or `() => value` used when the param is missing or parsing fails and it's not required. * `required`: navigation fails if absent (instead of using `default`). --- --- url: /guide/advanced/data-fetching.md --- # Data Fetching Sometimes you need to fetch data from the server when a route is activated. For example, before rendering a user profile, you need to fetch the user's data from the server. We can achieve this in two different ways: * **Fetching After Navigation**: perform the navigation first, and fetch data in the incoming component's lifecycle hook. Display a loading state while data is being fetched. * **Fetching Before Navigation**: Fetch data before navigation in the route enter guard, and perform the navigation after data has been fetched. Technically, both are valid choices - it ultimately depends on the user experience you are aiming for. ## Fetching After Navigation When using this approach, we navigate and render the incoming component immediately, and fetch data in the component itself. It gives us the opportunity to display a loading state while the data is being fetched over the network, and we can also handle loading differently for each view. Let's assume we have a `Post` component that needs to fetch the data for a post based on `route.params.id`: ::: code-group ```vue [Composition API] ``` ```vue [Options API] ``` ::: ## Fetching Before Navigation With this approach we fetch the data before actually navigating to the new route. We can perform the data fetching in the `beforeRouteEnter` guard in the incoming component, and only call `next` when the fetch is complete. The callback passed to `next` will be called **after the component is mounted**: ```js export default { data() { return { post: null, error: null, } }, async beforeRouteEnter(to, from, next) { try { const post = await getPost(to.params.id) // `setPost` is a method defined below next(vm => vm.setPost(post)) } catch (err) { // `setError` is a method defined below next(vm => vm.setError(err)) } }, // when route changes and this component is already rendered, // the logic will be slightly different. beforeRouteUpdate(to, from) { this.post = null getPost(to.params.id).then(this.setPost).catch(this.setError) }, methods: { setPost(post) { this.post = post }, setError(err) { this.error = err.toString() }, }, } ``` The user will stay on the previous view while the resource is being fetched for the incoming view. It is therefore recommended to display a progress bar or some kind of indicator while the data is being fetched. If the data fetch fails, it's also necessary to display some kind of global warning message. --- --- url: /data-loaders.md --- # Data Loaders Data loaders streamline any asynchronous state management with Vue Router, like **Data Fetching**. Adopting Data loaders ensures a consistent and efficient way to manage data fetching in your application. Keep all the benefits of using libraries like [Pinia Colada](./colada/) and integrate them seamlessly with client-side navigation. This is achieved by extracting the loading logic **outside** of the component `setup` (unlike ``). This way, the loading logic can be executed independently of the component life cycle, and the component can focus on rendering the data. Data Loaders are automatically collected and awaited within a navigation guard, ensuring the data is ready before rendering the component. ## Features * Parallel data fetching and deduplication * Automatic loading state management * Error handling * Extensible by loader implementations * SSR support * Prefetching data support ## Installation Install the `DataLoaderPlugin` **before the `router`**. ```ts{12-15} twoslash // @errors: 2769 2345 import { createApp } from 'vue' import { routes } from 'vue-router/auto-routes' import { createRouter, createWebHistory } from 'vue-router' import { DataLoaderPlugin } from 'vue-router/experimental' // [!code ++] const router = createRouter({ history: createWebHistory(), routes, }) const app = createApp({}) // Register the plugin before the router app.use(DataLoaderPlugin, { router }) // [!code ++] // adding the router will trigger the initial navigation app.use(router) app.mount('#app') ``` ## Quick start There are different data loader implementations. The simplest one is the [Basic Loader](./basic/) which always reruns data fetching. A more efficient one is the [Colada Loader](./colada/) which uses [@pinia/colada](https://github.com/posva/pinia-colada) under the hood. In the following examples, we will be using the *basic loader*. Loaders are [composables](https://vuejs.org/guide/reusability/composables.html) defined through a `defineLoader` function like `defineBasicLoader` or `defineColadaLoader`. They are *used* in the component `setup` to extract the needed information. To get started, *define* and ***export*** a loader from a **page** component: ::: code-group ```vue{2,5-7,11-16} twoslash [src/pages/users/[id].vue] ``` ::: The loader will automatically run when the route changes, for example when navigating to `/users/1`, even when coming from `/users/2`, the loader will fetch the data and delay the navigation until the data is ready. On top of that, you are free to *reuse* the returned composable `useUserData` in any other component, and it will automatically share the same data fetching instance. You can even [organize your loaders in separate files](./organization.md) as long as you **export** the loader from a **page** component. ## Why Data Loaders? Data fetching is the most common need for a web application. There are many ways of handling data fetching, and they all have their pros and cons. Data loaders are a way to streamline data fetching in your application. Instead of forcing you to choose between different libraries, data loaders provide a consistent way to manage data fetching in your application no matter the underlying library or strategy you use. --- --- url: /data-loaders/rfc.md --- # Data Loaders * Start Date: 2022-07-14 * Target Major Version: Vue 3, Vue Router 4 * Reference Issues: - * [Discussion](https://github.com/vuejs/rfcs/discussions/460) * [Implementation PR](https://github.com/posva/unplugin-vue-router/tree/main/src/data-loaders) ## Todo List List of things that haven't been added to the document yet: * \[ ] Extendable API for data fetching libraries like vue-apollo, vuefire, vue-query, etc * \[ ] Warn if a non lazy loader is used without data: meaning it was used in a component without it being exported by a page component. Either make it lazy or export it ## Summary There is no silver bullet to data fetching because of the different data fetching strategies and how they can define the architecture of the application and its UX. However, I think it's possible to find a solution that is flexible enough to **promote good practices** and **reduce the complexity** of data fetching in applications. That is the goal of this RFC, to standardize and improve data fetching with vue-router: * Integrate data fetching to the navigation cycle * Blocks navigation while fetching or *defer* less important data (known as *lazy* in Nuxt) * Deduplicate requests * Delay data updates until all data loaders are resolved * Avoids displaying partially up-to-date data and inconsistent state * Configurable through a `commit` option * Optimal data fetching * Defaults to parallel fetching * Semantic sequential fetching if needed * Avoid `` * No cascading loading states * No double mounting * [more...](#suspense) * Provide atomic and global access to loading/error states * Allow 3rd party libraries to extend the loaders functionality by establish a set of Interfaces that can be implemented. This targets libraries like [VueFire](https://vuefire.vuejs.org), [@pinia/colada][pinia-colada], [vue-apollo](https://apollo.vuejs.org/), [@tanstack/vue-query][vue-query], etc to provide features like caching, pagination, etc. specific to their use cases. This proposal concerns Vue Router 4 and is implemented under [unplugin-vue-router][uvr]. This enables types in data loaders but **is not necessary**. This feature is independent of the rest of the plugin and can be used without it, namely **without file-based routing**. ::: tip In this RFC, data loaders are often referred as *loaders* for short. API names also use the word *loader* instead of *data loader* for brevity. 💡 Some of the examples are interactive: hover or tap on the code to see the types and other information. ::: ## Basic example We create data loaders with a `defineLoader()` function that returns a **composable that can be used in any component** (not only pages component). The loader is then picked up by a Navigation Guard. It can be attached to a page component in two ways: * Export the loader from the page component it is attached to. It must be lazy loaded (`() => import('~/pages/users-details.vue')`) * Manually add the loader to the route definition's `meta.loaders[]` Exported from a non-setup ` ``` When a loader is exported by the page component, it is **automatically** picked up as long as the route is **lazy loaded** (which is a best practice). If the route isn't lazy loaded, the loader can be directly defined in an array of data loaders on `meta.loaders`: ```ts twoslash import './shims-vue.d' // ---cut--- // @moduleResolution: bundler import { createRouter, createWebHistory } from 'vue-router' import UserList from './pages/UserList.vue' // could be anywhere import { useUserList, useUserData, type User } from './loaders/users' export const router = createRouter({ history: createWebHistory(), routes: [ { path: '/users', component: UserList, meta: { // Required when the component is not lazy loaded loaders: [useUserList], }, }, { path: '/users/:id', // automatically picks up all exported loaders component: () => import('./pages/UserDetails.vue'), }, ], }) ``` Regarding the returned values from `useUserData()`: * `data` (aliased to `user`), `isLoading`, and `error` are shallow ref and therefore reactive. * `reload` is a function that can be called to force a reload of the data without a new navigation. Note `useUserData()` can be used in **any component**, not only in the page component: just import the function and call it within ` ``` The page component might not even use `useUserData()` but we can still use it anywhere else: ```vue ``` ::: warning If you use a loader in a component while it wasn't exported by a page, it won't be awaited during navigation. This can lead to unexpected behavior but it can be caught during development with a warning. ::: ### TypeScript Types are automatically generated for the routes by [unplugin-vue-router][uvr] and can be referenced with the name of each route to hint `defineLoader()` the possible values of the current types. On top of that, `defineLoader()` infers the returned types: ```vue twoslash ``` The arguments can be removed during the compilation step in production mode since they are only used for types and are ignored at runtime. ### Non blocking data fetching (Lazy Loaders) Also known as [lazy async data in Nuxt](https://v3.nuxtjs.org/api/composables/use-async-data), loaders can be marked as lazy to **not block the navigation**. ```vue{10,16-17} twoslash ``` This patterns is useful to avoid blocking the navigation while *non critical data* is being fetched. It will display the page earlier while some of the parts of it are still loading and you are able to display loader indicators thanks to the `isLoading` property. Note this still allows for having different behavior during SSR and client side navigation, e.g.: if we want to wait for the loader during SSR but not during client side navigation: ```ts{6-7} export const useUserData = defineLoader( async (route) => { // ... }, { lazy: !import.env.SSR, // Vite lazy: process.client, // NuxtJS } ) ``` Existing questions: * [~~Should it be possible to await all pending loaders with `await allPendingLoaders()`? Is it useful for SSR? Otherwise we could always ignore lazy loaders in SSR. Do we need both? Do we need to selectively await some of them?~~](https://github.com/vuejs/rfcs/discussions/460#discussioncomment-3532011) * Should we be able to transform a loader into a lazy version of it: `const useUserDataLazy = asLazyLoader(useUserData)` ### AbortSignal The loader receives in a second argument access to an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can be passed on to `fetch` and other Web APIs. If the navigation is cancelled because of errors or a new navigation, the signal aborts, causing any request using it to abort as well. ```ts twoslash import { defineBasicLoader as defineLoader } from 'vue-router/experimental' interface Book { title: string isbn: string description: string } function fetchBookCollection(options: { signal?: AbortSignal }): Promise { return {} as any } // ---cut--- export const useBookCollection = defineLoader(async (_route, { signal }) => { return fetchBookCollection({ signal }) }) ``` This aligns with the future [Navigation API](https://github.com/WICG/navigation-api#navigation-monitoring-and-interception) and other web APIs that use the `AbortSignal` to cancel an ongoing invocation. ### Implementations ### Interfaces Defining a minimal set of information and options for Data Loaders is what enables external libraries to implement their own data loaders. They are meant to extend these interfaces to add more features that are specific to them. You can see a practical example with the [Pinia Colada](./colada/) implementation. ::: danger This section is still a work in progress, see the [implementations](#implementations) instead. ::: ### Global API It's possible to access a global state of when data loaders are fetching (during navigation or when `reload()` is called) as well as when the data fetching navigation guard is running (only when navigating). * `isFetchingData: Ref`: is any loader currently fetching data? e.g. calling the `reload()` method of a loader * `isNavigationFetching: Ref`: is navigation being hold by a loader? (implies `isFetchingData.value === true`). Calling the `reload()` method of a loader doesn't change this. TBD: is this worth it? Are any other functions needed? ### Limitations * \~~Injections (`inject`/`provide`) cannot be used within a loader~~ They can now * Watchers and other composables shouldn't be used within data loaders: * if `await` is used before calling a composable e.g. `watch()`, the scope **is not guaranteed** * In practice, **this shouldn't be a problem** because there is **no need** to create composables within a loader ## Drawbacks * At first, it looks less intuitive than just awaiting something inside `setup()` with `` [but it doesn't have its limitations](#suspense) and have many more features * Requires an extra ` ``` Or when params are involved in the data fetching: ```vue ``` This setup has many limitations: * Nested routes will force **sequential data fetching**: it's not possible to ensure an **optimal parallel fetching** * Manual data refreshing is necessary **unless you add a `key` attribute** to the `` which will force a remount of the component on navigation. This is not ideal because it will remount the component on every navigation, even when the data is the same. It's necessary if you want to do a `` but less flexible than the proposed solution which also works with a `key` if needed. * By putting the fetching logic within the `setup()` of the component we face other issues: * No abstraction of the fetching logic => **code duplication** when fetching the same data in multiple components * No native way to deduplicate requests among multiple components using them: it requires using a store and extra logic to skip redundant fetches when multiple components are using the same data * Does not block the navigation * We can block it by mounting the upcoming page component (while the navigation is still blocked by the data loader navigation guard) which can be **expensive in terms of rendering and memory** as we still need to render the old page while we ***try** to mount the new page*. * Cannot modify the output of the navigation (e.g. redirecting, cancelling, etc), if the fetching fails, we end up in an error state * No native way of caching data, even for very simple cases (e.g. no refetching when fast traveling back and forward through browser UI) * Not possible to precisely read (or write) the loading state (see [vuejs/core#1347](https://github.com/vuejs/core/issues/1347)]) On top of this it's important to note that this RFC doesn't limit you: you can still use Suspense for data fetching or other async state or even use both, **this API is completely tree shakable** and doesn't add any runtime overhead if you don't use it. Aligning with the progressive enhancement nature of Vue.js. ### Other alternatives * Allowing blocking data loaders to return objects of properties: ::: details ```ts export const useUserData = defineLoader(async route => { const user = await getUserById(route.params.id) // instead of return user return { user } }) // instead of const { data: user } = useUserData() const { user } = useUserData() ``` This was the initial proposal but since this is not possible with lazy loaders it was more complex and less intuitive. Having one single version is overall easier to handle. It does allow to return pending promises in the object that aren't awaited: ```ts export const useUserData = defineLoader(async (route) => { return { // awaited user: await getUserById(route.params.id) // not awaited, like lazy nonCriticalData: getNonCriticalData() // Promise<...> } }) ``` But this version overlaps with `lazy: true`. While semantically it would be more natural if it was defined with **one** loader, it limits the API to one loader per page and not being able to reuse the data, loading state, error, etc across pages and components, which also limits the extensibility. ::: * Adding a new ` ``` Too magical without clear benefit. ::: * Pass route properties instead of the whole `route` object: ::: details ```ts import { getUserById } from '../api' export const useUserData = defineLoader(async ({ params }) => { const user = await getUserById(params.id) return { user } }) ``` This has the problem of not being able to use the `route.name` to determine the correct typed params (with [unplugin-vue-router][uvr]): ```ts import { getUserById } from '../api' export const useUserData = defineLoader(async route => { if (route.name === 'user-details') { const user = await getUserById(route.params.id) // ^ typed! return { user } } }) ``` ::: * Naming ::: details Variables could be named differently and proposals are welcome: * `isLoading` -> `isPending`, `pending` (same as Nuxt) * Rename `defineLoader()` to `defineDataFetching()` (or others) ::: * Nested/Sequential Loaders drawbacks ::: details * Allowing `await getUserById()` could make people think they should also await inside ` ``` Note that lazy loaders can only control their own blocking mechanism. They can't control the blocking of other loaders. If multiple loaders are being used and one of them is blocking, the navigation will be blocked until all of the blocking loaders are resolved. A function could allow to conditionally block upon navigation: ```ts export const useUserData = defineLoader( loader, // ... { lazy: route => { // ... return true // or a number }, } ) ``` ::: * One could argue being able to reuse the result of loaders across any component other than page makes this more complex. Other frameworks expose a single *load* function from page components (SvelteKit, Remix) ## Adoption strategy Introduce this as part of [unplugin-vue-router][uvr] to test it first and make it part of the router later on. ## Unresolved questions * Integration with Server specifics in Frameworks like Nuxt: cookies, headers, server only loaders (can create redirect codes) * Should there by a `beforeLoad()` hook that is called and awaited before all data loaders * Same for `afterLoad()` that is always called after all data loaders * What else is needed besides the `route` inside loaders? * \~~Add option for placeholder data?~~ Data Loaders should implement this themselves * What other operations might be necessary for users? [uvr]: https://github.com/posva/unplugin-vue-router "unplugin-vue-router" [pinia-colada]: https://github.com/posva/pinia-colada "@pinia/colada" [vue-query]: https://tanstack.com/query/latest/docs/framework/vue/overview "@tanstack/vue-query" --- --- url: /data-loaders/defining-loaders.md --- # Defining Data Loaders In order to use data loaders, you need to define them first. Data loaders themselves are the composables returned by the different `defineLoader` functions. Each loader definition is specific to the `defineLoader` function used. For example, `defineBasicLoader` expects an async function as the first argument while `defineColadaLoader` expects an object with a `query` function. All loaders should allow to pass an async function that can throw errors, and call `reroute()` to control the navigation. Any composables returned by *any* `defineLoader` function share the same signature: ```vue twoslash ``` **But they are not limited by it!** For example, the `defineColadaLoader` function returns a composable with a few more properties like `status` and `refresh`. Because of this it's important to refer to the documentation of the specific loader you are using. This page will guide you through the **foundation** of defining data loaders, no matter their implementation. ## The loader function The loader function is the *core* of data loaders. They are asynchronous functions that return the data you want to expose in the `data` property of the returned composable. ### The `to` argument The `to` argument represents the location object we are navigating to. It should be used as the source of truth for all data fetching parameters. ```ts twoslash import 'vue-router/auto-routes' import { defineBasicLoader } from 'vue-router/experimental' import { getUserById } from '../api' // ---cut--- export const useUserData = defineBasicLoader('/users/[id]', async to => { const user = await getUserById(to.params.id) // here we can modify the data before returning it return user }) ``` By using the route location to fetch data, we ensure a consistent relationship between the data and the URL, **improving the user experience**. ### Side effects It's important to avoid side effects in the loader function. Don't call `watch`, or create reactive effects like `ref`, `toRefs()`, `computed`, etc. ### Global Properties In the loader function, you can access global properties like the router instance, a store, etc. This is because using `inject()` within the loader function **is possible**, just like within navigation guards. Since loaders are asynchronous, make sure you are using the `inject` function **before any `await`**: ```ts twoslash import 'vue-router/auto-routes' import { defineBasicLoader } from 'vue-router/experimental' import { getUserById } from '../api' // ---cut--- import { inject } from 'vue' import { useSomeStore, useOtherStore } from '@/stores' export const useUserData = defineBasicLoader('/users/[id]', async to => { // ✅ This will work const injectedValue = inject('key') // [!code ++] const store = useSomeStore() // [!code ++] const user = await getUserById(to.params.id) // ❌ These won't work const injectedValue2 = inject('key-2') // [!code error] const store2 = useOtherStore() // [!code error] // ... return user }) ``` ### Navigation control Since loaders happen within the context of a navigation, you can control the navigation by calling `reroute()`. This is similar to returning a value in a navigation guard. It throws internally, so execution stops immediately. ```ts{1,8,9} import { reroute } from 'vue-router/experimental' const useDashboardStats = defineBasicLoader('/admin', async (to) => { try { return await getDashboardStats() } catch (err) { if (err.code === 401) { // same as returning '/login' in a navigation guard reroute('/login') } throw err // unexpected error } }) ``` ::: tip Note that [lazy loaders](#lazy-loaders) cannot control the navigation since they do not block it. ::: Read more in the [Navigation Aware](./navigation-aware.md) section. ### Errors Any thrown Error will abort the navigation, just like in navigation guards. They will trigger the `router.onError` handler if defined. ::: tip Note that [lazy loaders](#lazy-loaders) cannot control the navigation since they do not block it, any thrown error will appear in the `error` property and not abort the navigation nor appear in the `router.onError` handler. ::: It's possible to define expected errors so they don't abort the navigation. You can read more about it in the [Error Handling](./error-handling.md) section. ## Options Data loaders are designed to be flexible and allow for customization. Despite being navigation-centric, they can be used outside of a navigation and this flexibility is key to their design. ### Non blocking loaders with `lazy` By default, loaders are *non-lazy*, meaning they will block the navigation until the data is fetched. But this behavior can be changed by setting the `lazy` option to `true`. ```vue{10,16} twoslash ``` This patterns is useful to avoid blocking the navigation while *non critical data* is being fetched. It will display the page earlier while lazy loaders are still loading and you are able to display loader indicators thanks to the `isLoading` property. Since lazy loaders do not block the navigation, any thrown error will not abort the navigation nor appear in the `router.onError` handler. Instead, the error will be available in the `error` property. Note this still allows for having different behavior during SSR and client side navigation, e.g.: if we want to wait for the loader during SSR but not during client side navigation: ```ts{6-7} export const useUserData = defineBasicLoader( async (to) => { // ... }, { lazy: !import.meta.env.SSR, // Vite specific } ) ``` You can even pass a function to `lazy` to determine if the loader should be lazy or not based on each load/navigation: ```ts{6-7} export const useSearchResults = defineBasicLoader( async (to) => { // ... }, { // lazy if we are on staying on the same route lazy: (to, from) => to.name === from.name, } ) ``` This is really useful when you can display the old data while fetching the new one and some of the parts of the page require the route to be updated like search results and pagination buttons. By using a lazy loader only when the route changes, the pagination can be updated immediately while the search results are being fetched, allowing the user to click multiple times on the pagination buttons without waiting for the search results to be fetched. ### Delaying data updates with `commit` By default, the data is updated only once all loaders are resolved. This is useful to avoid displaying partially loaded data or worse, incoherent data aggregation. Sometimes you might want to immediately update the data as soon as it's available, even if other loaders are still pending. This can be achieved by changing the `commit` option: ```ts twoslash import { defineBasicLoader } from 'vue-router/experimental' interface Book { title: string isbn: string description: string } function fetchBookCollection(): Promise { return {} as any } // ---cut--- export const useBookCollection = defineBasicLoader(fetchBookCollection, { commit: 'immediate', }) ``` In the case of [lazy loaders](#lazy-loaders), they also default to `commit: 'after-load'`. They will commit after all other non-lazy loaders if they can but since they are not awaited, they might not be able to. In this case, the data will be available when finished loading, which can be much later than the navigation is completed. ### Server optimization with `server` During SSR, it might be more performant to avoid loading data that isn't critical for the initial render. This can be achieved by setting the `server` option to `false`. That will completely skip the loader during SSR. ```ts{3} twoslash import { defineBasicLoader } from 'vue-router/experimental' interface Book { title: string isbn: string description: string } function fetchRelatedBooks(id: string | string[]): Promise { return {} as any } // ---cut--- export const useRelatedBooks = defineBasicLoader( '/books/[id]', (to) => fetchRelatedBooks(to.params.id), { server: false } ) ``` You can read more about server side rendering in the [SSR](./ssr.md) section. ## Connecting a loader to a page The router needs to know what loaders should be ran with which page. This is achieved in two ways: * **Automatically**: when a loader is exported from a page component that is lazy loaded, the loader will be automatically connected to the page ::: code-group ```ts{8} [router.ts] import { createRouter, createWebHistory } from 'vue-router' export const router = createRouter({ history: createWebHistory(), routes: [ { path: '/settings', component: () => import('./settings.vue'), }, ], }) ``` ```vue{3-5} [settings.vue] ``` ::: * **Manually**: by passing the defined loader into the `meta.loaders` property: ::: code-group ```ts{2,10-12} [router.ts] import { createRouter, createWebHistory } from 'vue-router' import Settings, { useSettings } from './settings.vue' export const router = createRouter({ history: createWebHistory(), routes: [ { path: '/settings', component: Settings, meta: { loaders: [useSettings], }, } ], }) ``` ```vue{3-5} [settings.vue] ``` ### *Disconnecting* a loader from a page It is also possible **not to connect a loader to a page**. This allows you to delay the loading until the component is mounted. Usually you want to start loading the data as soon as possible but in some cases, it might be better to wait until the component is mounted. This can be achieved by not exporting the loader from the page component. --- --- url: /guide/essentials/history-mode.md --- # Different History modes The `history` option when creating the router instance allows us to choose among different history modes. ## HTML5 Mode The HTML5 mode is created with `createWebHistory()` and is the recommended mode: ```js import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ //... ], }) ``` When using `createWebHistory()`, the URL will look "normal," e.g. `https://example.com/user/id`. Beautiful! Here comes a problem, though: Since our app is a single page client side app, without a proper server configuration, the users will get a 404 error if they access `https://example.com/user/id` directly in their browser. Now that's ugly. Not to worry: To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn't match any static assets, it should serve the same `index.html` page that your app lives in. Beautiful, again! ## Hash Mode The hash history mode is created with `createWebHashHistory()`: ```js import { createRouter, createWebHashHistory } from 'vue-router' const router = createRouter({ history: createWebHashHistory(), routes: [ //... ], }) ``` It uses a hash character (`#`) before the actual URL that is internally passed. Because this section of the URL is never sent to the server, it doesn't require any special treatment on the server level. **It does however have a bad impact in SEO**. If that's a concern for you, use the HTML5 history mode. ## Memory mode The memory history mode doesn't assume a browser environment and therefore doesn't interact with the URL **nor automatically triggers the initial navigation**. This makes it perfect for Node environment and SSR. It is created with `createMemoryHistory()` and **requires you to push the initial navigation** after calling `app.use(router)`. ```js import { createRouter, createMemoryHistory } from 'vue-router' const router = createRouter({ history: createMemoryHistory(), routes: [ //... ], }) ``` While it's not recommended, you can use this mode inside Browser applications but note **there will be no history**, meaning you won't be able to go *back* or *forward*. ## Example Server Configurations **Note**: The following examples assume you are serving your app from the root folder. If you deploy to a subfolder, you should use [the `publicPath` option of Vue CLI](https://cli.vuejs.org/config/#publicpath) and the related [`base` property of the router](/api/functions/createWebHistory.md#base-). You also need to adjust the examples below to use the subfolder instead of the root folder (e.g. replacing `RewriteBase /` with `RewriteBase /name-of-your-subfolder/`). ### Apache ``` Options -MultiViews RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.html [L] ``` Instead of `mod_rewrite`, you could also use [`FallbackResource`](https://httpd.apache.org/docs/2.4/mod/mod_dir.html#fallbackresource). ### nginx ```nginx location / { try_files $uri $uri/ /index.html; } ``` For a standalone server config (e.g. when using the official `nginx` docker image), drop the following into `/etc/nginx/conf.d/default.conf`: ```nginx server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } } ``` ### Native Node.js ```js const http = require('http') const fs = require('fs') const httpPort = 80 http .createServer((req, res) => { fs.readFile('index.html', 'utf-8', (err, content) => { if (err) { console.log('We cannot open "index.html" file.') } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', }) res.end(content) }) }) .listen(httpPort, () => { console.log('Server listening on: http://localhost:%s', httpPort) }) ``` ### Express with Node.js For Node.js/Express, consider using [connect-history-api-fallback middleware](https://github.com/bripkens/connect-history-api-fallback). ### Internet Information Services (IIS) 1. Install [IIS UrlRewrite](https://www.iis.net/downloads/microsoft/url-rewrite) 2. Create a `web.config` file in the root directory of your site with the following: ```xml [web.config ~vscode-icons:file-type-xml~] ``` ### Caddy v2 ``` try_files {path} / ``` ### Caddy v1 ``` rewrite { regexp .* to {path} / } ``` ### Firebase hosting Add this to your `firebase.json`: ```json [firebase.json ~vscode-icons:file-type-firebase~] { "hosting": { "public": "dist", "rewrites": [ { "source": "**", "destination": "/index.html" } ] } } ``` ### Netlify Create a `_redirects` file that is included with your deployed files: ```[_redirects ~vscode-icons:file-type-light-netlify~] /* /index.html 200 ``` In vue-cli, nuxt, and vite projects, this file usually goes under a folder named `static` or `public`. You can read more about the syntax on [Netlify documentation](https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps). You can also [create a `netlify.toml`](https://docs.netlify.com/configure-builds/file-based-configuration/) to combine *redirections* with other Netlify features. ### Vercel Create a `vercel.json` file under the root directory of your project with the following: ```json [vercel.json ~vscode-icons:file-type-light-vercel~] { "rewrites": [{ "source": "/:path*", "destination": "/index.html" }] } ``` ### Azure Static Web Apps Create a `staticwebapp.config.json` file in the public folder with the following configuration. If the requested route does not exist, Azure will rewrite all requests to `index.html` except for assets and favicons. ```json [staticwebapp.config.json ~vscode-icons:file-type-light-azure~] { "navigationFallback": { "rewrite": "/index.html", "exclude": ["/assets/*", "/favicons/*"] } } ``` ## Caveat There is a caveat to this: Your server will no longer report 404 errors as all not-found paths now serve up your `index.html` file. To get around the issue, you should implement a catch-all route within your Vue app to show a 404 page: ```js const router = createRouter({ history: createWebHistory(), routes: [{ path: '/:pathMatch(.*)', component: NotFoundComponent }], }) ``` Alternatively, if you are using a Node.js server, you can implement the fallback by using the router on the server side to match the incoming URL and respond with 404 if no route is matched. Check out the [Vue server side rendering documentation](https://vuejs.org/guide/scaling-up/ssr.html) for more information. --- --- url: /guide/essentials/dynamic-matching.md --- # Dynamic Route Matching with Params Very often we will need to map routes with the given pattern to the same component. For example, we may have a `User` component which should be rendered for all users but with different user IDs. In Vue Router we can use a dynamic segment in the path to achieve that, we call that a *param*: ```js import User from './User.vue' // these are passed to `createRouter` const routes = [ // dynamic segments start with a colon { path: '/users/:id', component: User }, ] ``` Now URLs like `/users/johnny` and `/users/jolyne` will both map to the same route. A *param* is denoted by a colon `:`. When a route is matched, the value of its *params* will be exposed as `route.params` in every component. Therefore, we can render the current user ID by updating `User`'s template to this: ```vue ``` You can have multiple *params* in the same route, and they will map to corresponding fields on `route.params`. Examples: | pattern | matched path | route.params | | ------------------------------ | ------------------------ | ---------------------------------------- | | /users/:username | /users/eduardo | `{ username: 'eduardo' }` | | /users/:username/posts/:postId | /users/eduardo/posts/123 | `{ username: 'eduardo', postId: '123' }` | * [See it in the Playground](https://play.vuejs.org/#eNqdVOtu0zAUfhUrIDWVlrgdF6GQVYNpEkMCpgG/CD+yxm29ObZlO22nqu/O8SWX0W5IVErjnOt3zvmOd1FdUp7e6SiLaC2FMmiH5oqUhnyQEu3RQokajdYNGRW8M1CiMUR12hR7QW9ifVtdKWXq/Qvu7VLZ6FU8wo0mSmNSNaWqBJZCG42no3FqVoTH8RidzdCu4KjHE8MzthKEUnCOfbhWUouGm3j0AhKOQLaHJzqJQnqoLzeklgwizaxDvprObqw/kqUqa51jEDiFdC84aKMEX84uGqUIN75qsDarLMdBh3Y79NIp0kXD2DUo0X7vwmAfJ+flug3YsHCCM6PdGb58JQmj/B4ZcVZEx5tTRLPjijyMwEXok+BBlv/KeDp5KuXp5J85cxwKznHbhNzSrVUH9zUlG4SDYdDneDAs+NTmgRGk50KSCiTpIHNCtuXcJPDQNQmEEUyoDHhDay34e+CCjehigDOQIvDwGO2/kFqoh09UG3idBKEjSk944FPSUb4j/U/o0jX0pmN+K+joT7bOcC44WIUlOnuUInb4Vz67reAAUjw+sSbOW2fol++lc7M/R0/U7lZmX7ysSRhaZl9X1ciFsL+5AOwc2J118INu796/4T8s0rCaw22qaMtyazdYC79ddl0dENgOtKGwJRbJoZnH162QjzogA+Dw3U7qUsL8BAcorvgiKHQRZW07iqiflBUX0coYqTOMGy7vlylUj3uL8zfpJH0LObUZSFOi6+RWiQ0UAAmLKPSniM7BCFdkbYRgOiklfSrFgeH5u3SaTvtMQ91BPpsO2rGH0g0sAF/Q5V+F2yFSRtQ3aShQ61EDSsbE5rOTGdWQDvx8Reb3R+R3euvLuFYEEKzJoGBTqiUxXn35/SvZwrlT1qJqGFg/o7whWrDGYvRmHxteAeyBnUN75eZI+fKHvtwawnVblAXquuHs3XAvnim9h/sqfT3oorsHdDrXdvvhYjpB9tLxfrdCVQSujlO5RQCWVujFZDKBGwSBkVpSntwKY0SdoakitZPLsqoAbCeBLAWHsKhhPiaMGcgL27xgZOtcGAw+cTAyxGH9HscZpvNfXUhGMy5MnLESAohFYh4kGfssAZ6iyxUsc48l2v8BszmoiA==) In addition to `route.params`, the `route` object also exposes other useful information such as `route.query` (if there is a query in the URL), `route.hash`, etc. You can check out the full details in the [API Reference](../../api/#RouteLocationNormalized). ## Reacting to Params Changes One thing to note when using routes with params is that when the user navigates from `/users/johnny` to `/users/jolyne`, **the same component instance will be reused**. Since both routes render the same component, this is more efficient than destroying the old instance and then creating a new one. **However, this also means that some lifecycle hooks of the component will not be called**. To react to params changes in the same component, you can simply watch anything on the `route` object, in this scenario, the `route.params`: ::: code-group ```vue [Composition API] ``` ```vue [Options API] ``` ::: Or, use the `beforeRouteUpdate` [navigation guard](../advanced/navigation-guards.md), which also allows you to cancel the navigation: ::: code-group ```vue [Composition API] ``` ```vue [Options API] ``` ::: ## Catch all / 404 Not found Route Regular params will only match characters in between url fragments, separated by `/`. If we want to match **anything**, we can use a custom *param* regexp by adding the regexp inside parentheses right after the *param*: ```js const routes = [ // will match everything and put it under `route.params.pathMatch` { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, // will match anything starting with `/user-` and put it under `route.params.afterUser` { path: '/user-:afterUser(.*)', component: UserGeneric }, ] ``` In this specific scenario, we are using a [custom regexp](./route-matching-syntax.md#custom-regexp-in-params) between parentheses and marking the `pathMatch` param as [optionally repeatable](./route-matching-syntax.md#optional-parameters). This allows us to directly navigate to the route if we need to by splitting the `path` into an array: ```js router.push({ name: 'NotFound', // preserve current path and remove the first char to avoid the target URL starting with `//` params: { pathMatch: route.path.substring(1).split('/') }, // preserve existing query and hash if any query: route.query, hash: route.hash, }) ``` See more in the [repeated params](./route-matching-syntax.md#Repeatable-params) section. If you are using [History mode](./history-mode.md), make sure to follow the instructions to correctly configure your server as well. ## Advanced Matching Patterns Vue Router uses its own path matching syntax, inspired by the one used by `express`, so it supports many advanced matching patterns such as optional params, zero or more / one or more requirements, and even custom regex patterns. Please check the [Advanced Matching](./route-matching-syntax.md) documentation to explore them. --- --- url: /guide/advanced/dynamic-routing.md --- # Dynamic Routing Adding routes to your router is usually done via the `routes` option but in some situations, you might want to add or remove routes while the application is already running. Applications with extensible interfaces like [Vue CLI UI](https://cli.vuejs.org/dev-guide/ui-api.html) can use this to make the application grow. ## Adding routes Dynamic routing is achieved mainly via two functions: `router.addRoute()` and `router.removeRoute()`. They **only** register a new route, meaning that if the newly added route matches the current location, it would require you to **manually navigate** with `router.push()` or `router.replace()` to display that new route. Let's take a look at an example: Imagine having the following router with one single route: ```js const router = createRouter({ history: createWebHistory(), routes: [{ path: '/:articleName', component: Article }], }) ``` Going to any page like `/about`, `/store`, or `/3-tricks-to-improve-your-routing-code` ends up rendering the `Article` component. If we are on `/about` and we add a new route: ```js router.addRoute({ path: '/about', component: About }) ``` The page will still show the `Article` component. We need to manually call `router.replace()` to change the current location and overwrite where we were (instead of pushing a new entry, ending up in the same location twice in our history): ```js router.addRoute({ path: '/about', component: About }) // we could also use this.$route or useRoute() router.replace(router.currentRoute.value.fullPath) ``` Remember you can `await router.replace()` if you need to wait for the new route to be displayed. ## Adding routes inside navigation guards If you decide to add or remove routes inside of a navigation guard, you should not call `router.replace()` but trigger a redirection by returning the new location: ```js router.beforeEach(to => { if (!hasNecessaryRoute(to)) { router.addRoute(generateRoute(to)) // trigger a redirection return to.fullPath } }) ``` The example above assumes two things: first, the newly added route record will match the `to` location, effectively resulting in a different location from the one we were trying to access. Second, `hasNecessaryRoute()` returns `true` after adding the new route to avoid an infinite redirection. Because we are redirecting, we are replacing the ongoing navigation, effectively behaving like the example shown before. In real world scenarios, adding is more likely to happen outside of navigation guards, e.g. when a view component mounts, it register new routes. ## Removing routes There are few different ways to remove existing routes: * By adding a route with a conflicting name. If you add a route that has the same name as an existing route, it will remove the route first and then add the route: ```js router.addRoute({ path: '/about', name: 'about', component: About }) // this will remove the previously added route because they have // the same name and names are unique across all routes router.addRoute({ path: '/other', name: 'about', component: Other }) ``` * By calling the callback returned by `router.addRoute()`: ```js const removeRoute = router.addRoute(routeRecord) removeRoute() // removes the route if it exists ``` This is useful when the routes do not have a name * By using `router.removeRoute()` to remove a route by its name: ```js router.addRoute({ path: '/about', name: 'about', component: About }) // remove the route router.removeRoute('about') ``` Note you can use `Symbol`s for names in routes if you wish to use this function but want to avoid conflicts in names. Whenever a route is removed, **all of its aliases and children** are removed with it. ## Adding nested routes To add nested routes to an existing route, you can pass the *name* of the route as its first parameter to `router.addRoute()`. This will effectively add the route as if it was added through `children`: ```js router.addRoute({ name: 'admin', path: '/admin', component: Admin }) router.addRoute('admin', { path: 'settings', component: AdminSettings }) ``` This is equivalent to: ```js router.addRoute({ name: 'admin', path: '/admin', component: Admin, children: [{ path: 'settings', component: AdminSettings }], }) ``` ## Looking at existing routes Vue Router gives you two functions to look at existing routes: * [`router.hasRoute()`](/api/interfaces/RouterClassic.md#hasRoute-): check if a route exists. * [`router.getRoutes()`](/api/interfaces/RouterClassic.md#getRoutes-): get an array with all the route records. --- --- url: /data-loaders/error-handling.md --- # Error handling By default, all errors thrown in a loader are considered *unexpected errors*: they will abort the navigation, just like in a navigation guard. Because they abort the navigation, they will not appear in the `error` property of the loader. Instead, they will be intercepted by Vue Router's error handling with `router.onError()`. However, if the loader is **not navigation-aware**, the error cannot be intercepted by Vue Router and will be kept in the `error` property of the loader. This is the case for *lazy loaders* and [*reloading data*](./reloading-data.md). ## Defining expected Errors To be able to intercept errors in non-lazy loaders, we can specify a list of error classes that are considered *expected errors*. This allows blocking loader to **not abort the navigation** and instead keep the error in the `error` property of the loader and let the page locally display the error state. ```ts{3-10,14,18} twoslash import { defineBasicLoader } from 'vue-router/experimental' // custom error class class MyError extends Error { // override is only needed in TS override name = 'MyError' // Displays in logs instead of 'Error' // defining a constructor is optional constructor(message: string) { super(message) } } export const useUserData = defineBasicLoader( async (to) => { throw new MyError('Something went wrong') // ... // ---cut-start--- return { name: 'John' } // ---cut-end--- }, { errors: [MyError], } ) ``` You can also specify *expected errors* globally for all loaders by providing the `errors` option to the `DataLoaderPlugin`. ```ts{4} twoslash import { createApp } from 'vue' import type { Router } from 'vue-router' import { DataLoaderPlugin } from 'vue-router/experimental' const app = createApp({}) const router = {} as Router class MyError extends Error { name = 'MyError' constructor(message: string) { super(message) } } // @errors: 2769 // ---cut--- app.use(DataLoaderPlugin, { router, // checks with `instanceof MyError` errors: [MyError], }) ``` Then you need to opt-in in the loader by setting the `errors` option to `true` to keep the error in the `error` property of the loader. ```ts{7} twoslash import { defineBasicLoader } from 'vue-router/experimental' // ---cut--- export const useUserData = defineBasicLoader( async (to) => { throw new Error('Something went wrong') // ... // ---cut-start--- return { name: 'John' } // ---cut-end--- }, { errors: true, } ) ``` ::: details Why is `errors: true` needed? One of the benefits of Data Loaders is that they ensure the `data` to be ready before the component is rendered. With expected errors, this is no longer true and `data` can be `undefined`: ```ts{11} twoslash import { defineBasicLoader } from 'vue-router/experimental' // ---cut--- export const useDataWithErrors = defineBasicLoader( async (to) => { // ... // ---cut-start--- return { name: 'John' } // ---cut-end--- }, { errors: true, } ) const { data } = useDataWithErrors() data.value // `data` can be `undefined` ``` ::: ## Custom Error handling If you need more control over the error handling, you can provide a function to the `errors` option. This option is available in both the `DataLoaderPlugin` and when defining a loader. ```ts{3-9} twoslash // @errors: 2769 import { createApp } from 'vue' import { DataLoaderPlugin } from 'vue-router/experimental' const app = createApp({}) const router = {} as any // ---cut--- app.use(DataLoaderPlugin, { router, errors: (error) => { // Convention for custom errors if (error instanceof Error && error.name?.startsWith('My')) { return true } return false // unexpected error }, }) ``` ## Handling both, local and global errors TODO: this hasn't been implemented yet ## Error handling priority When you use both, global and local error handling, the local error handling has a higher priority and will override the global error handling. This is how the local and global errors are checked: * if local `errors` is `false`: abort the navigation -> `data` is not `undefined` * if local `errors` is `true`: rely on the globally defined `errors` option -> `data` is possibly `undefined` * else: rely on the local `errors` option -> `data` is possibly `undefined` ## TypeScript You will notice that the type of `error` is `Error | null` even when you specify the `errors` option. This is because if we call the `reload()` method (meaning we are outside of a navigation), the error isn't discarded, it appears in the `error` property **without being filtered** by the `errors` option. In practice, depending on how you handle the error, you will add a [type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards) inside the component responsible for displaying an error or directly in a `v-if` in the template. ```vue-html ``` If you want to be even stricter, you can override the default `Error` type with `unknown` (or anything else) by augmenting the `TypesConfig` interface. ```ts // types-extension.d.ts import 'vue-router' export {} declare module 'vue-router' { interface TypesConfig { Error: unknown } } ``` --- --- url: /file-based-routing/eslint.md --- # ESLint If you are not using auto imports, you will need to tell ESLint about `vue-router/auto-routes`. Add these lines to your eslint configuration: ```json{3} { "settings": { "import/core-modules": ["vue-router/auto-routes"] } } ``` ## `definePage()` Since `definePage()` is a global macro, you need to tell ESLint about it. Add these lines to your eslint configuration: ```json{3} { "globals": { "definePage": "readonly" } } ``` --- --- url: /experimental/router-resolver.md --- # Experimental Router ::: warning The experimental router reflects the explorations of the upcoming major version of Vue Router. It is not production-ready and should be used for testing and feedback purposes only. ::: The experimental router introduces a new **resolver-based** matching layer that powers stronger typing, file-based routing, and **custom param parsers**. ## Installation The experimental router lives next to the stable one and is opt-in. You import the factory from `vue-router/experimental` and a resolver (from `vue-router/auto-resolver` when using file-based routing): ```ts{3,4,8} // src/router/index.ts import { createWebHistory } from 'vue-router' import { experimental_createRouter as createRouter } from 'vue-router/experimental' import { resolver, handleHotUpdate } from 'vue-router/auto-resolver' export const router = createRouter({ history: createWebHistory(), resolver, }) if (import.meta.hot) { handleHotUpdate(router) } ``` Since the experimental router doesn't add the `` and `` components, you need to register them globally: ```ts{3,8,9} // src/main.ts import { createApp } from 'vue' import { RouterLink, RouterView } from 'vue-router' import App from './App.vue' import { router } from './router' const app = createApp(App) app.component('RouterLink', RouterLink) app.component('RouterView', RouterView) app.use(router) app.mount('#app') ``` ## Opt-in to typed `useRouter()` / `useRoute()` To get a stricter router instance type from `useRouter()`, register your router on `TypesConfig`: ```ts // src/main.ts declare module 'vue-router' { export interface TypesConfig { Router: typeof router } } ``` ## With Data Loaders If you use [Data Loaders](../data-loaders/), install the plugin **before** the router: ```ts import { DataLoaderPlugin } from 'vue-router/experimental' app.use(DataLoaderPlugin, { router }) app.use(router) ``` --- --- url: /guide/advanced/extending-router-link.md --- # Extending RouterLink The RouterLink component exposes enough `props` to suffice most basic applications but it doesn't try to cover every possible use case and you will likely find yourself using `v-slot` for some advanced cases. In most medium to large sized applications, it's worth creating one if not multiple custom RouterLink components to reuse them across your application. Some examples are Links in a Navigation Menu, handling external links, adding an `inactive-class`, etc. Let's extend RouterLink to handle external links as well and adding a custom `inactive-class` in an `AppLink.vue` file: ::: code-group ```vue [Composition API] ``` ```vue [Options API] ``` ::: If you prefer using a render function or create `computed` properties, you can use the `useLink` from the [Composition API](./composition-api.md): ```js import { RouterLink, useLink } from 'vue-router' export default { name: 'AppLink', props: { // add @ts-ignore if using TypeScript ...RouterLink.props, inactiveClass: String, }, setup(props) { // `props` contains `to` and any other prop that can be passed to const { navigate, href, route, isActive, isExactActive } = useLink(props) // profit! return { isExternalLink } }, } ``` In practice, you might want to use your `AppLink` component for different parts of your application. e.g. using [Tailwind CSS](https://tailwindcss.com), you could create a `NavLink.vue` component with all the classes: ```vue ``` --- --- url: /file-based-routing/extending-routes.md --- # Extending Routes ## Extending routes in config You can extend the routes at build time with the `extendRoute` or the `beforeWriteFiles` options. Both can return a Promise: ```ts import VueRouter from 'vue-router/vite' import path from 'node:path' VueRouter({ extendRoute(route) { if (route.name === '/[name]') { route.addAlias('/hello-vite-:name') } }, beforeWriteFiles(root) { root.insert('/from-root', path.join(__dirname, './src/pages/index.vue')) }, }) ``` Routes modified this way will be reflected in the generated `typed-router.d.ts` file. ## In-Component Routing It's possible to override the route configuration directly in the page component file. These changes are picked up by the plugin and reflected in the generated `typed-router.d.ts` file. ### `definePage()` You can modify and extend any page component with the `definePage()` macro. This is useful for adding meta information, or modifying the route object. It's globally available in Vue components but you can import it from `vue-router` if needed. ```vue{2-7} ``` If you are using ESLint, you will need [to declare it as a global variable](./eslint#definepage). ::: danger You cannot use variables in `definePage()` as its passed parameter gets extracted at build time and is removed from ` ``` It's not necessary to understand all of that code right now. The key thing to notice is that the composables `useRouter()` and `useRoute()` are used to access the router instance and current route respectively. ### Next steps If you'd like to see a complete example using Vite, you can use the [create-vue](https://github.com/vuejs/create-vue) scaffolding tool, which has the option to include Vue Router in its example project: ::: code-group ```bash [npm] npm create vue@latest ``` ```bash [yarn] yarn create vue ``` ```bash [pnpm] pnpm create vue ``` ::: The example project created by create-vue uses similar features to the ones we've seen here. You may find that a useful starting point for exploring the features introduced in the next few pages of this guide. ## Conventions in this guide ### Single-File Components Vue Router is most commonly used in applications built using a bundler (e.g. Vite) and [SFCs](https://vuejs.org/guide/introduction.html#single-file-components) (i.e. `.vue` files). Most of the examples in this guide will be written in that style, but Vue Router itself doesn't require you to use build tools or SFCs. For example, if you're using the *global builds* of [Vue](https://vuejs.org/guide/quick-start.html#using-vue-from-cdn) and [Vue Router](../installation#Direct-Download-CDN), the libraries are exposed via global objects, rather than imports: ```js const { createApp } = Vue const { createRouter, createWebHistory } = VueRouter ``` ### Component API style Vue Router can be used with both the Composition API and the Options API. Where relevant, the examples in this guide will show components written in both styles. Composition API examples will typically use ` ``` `@vue/devtools-api` is only needed when using the development build of Vue Router. It can be removed when using the production build, `vue-router.esm-browser.prod.js`. ### Using the global build ```html-vue ``` The corresponding production build of Vue Router is called `vue-router.global.prod.js`. --- --- url: /introduction.md --- # Introduction Watch a Free Vue Router Video Course Vue Router is the official router for [Vue.js](https://vuejs.org). It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze. Features include: * Nested routes mapping * Dynamic Routing * Modular, component-based router configuration * Route params, query, wildcards * View transition effects powered by Vue.js' transition system * Fine-grained navigation control * Links with automatic active CSS classes * HTML5 history mode or hash mode * Customizable Scroll Behavior * Proper encoding for URLs [Get started](./guide/) or play with the [playground](https://github.com/vuejs/router/tree/main/packages/playground) (see [`README.md`](https://github.com/vuejs/router) to run them). --- --- url: /guide/advanced/lazy-loading.md --- # Lazy Loading Routes When building apps with a bundler, the JavaScript bundle can become quite large, and thus affect the page load time. It would be more efficient if we can split each route's components into separate chunks, and only load them when the route is visited. Vue Router supports [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) out of the box, meaning you can replace static imports with dynamic ones: ```js // replace // import UserDetails from './views/UserDetails.vue' // with const UserDetails = () => import('./views/UserDetails.vue') const router = createRouter({ // ... routes: [ { path: '/users/:id', component: UserDetails }, // or use it directly in the route definition { path: '/users/:id', component: () => import('./views/UserDetails.vue') }, ], }) ``` The `component` (and `components`) option accepts a function that returns a Promise of a component and Vue Router **will only fetch it when entering the page for the first time**, then use the cached version. Which means you can also have more complex functions as long as they return a Promise: ```js const UserDetails = () => Promise.resolve({/* component definition */}) ``` In general, it's a good idea **to always use dynamic imports** for all your routes. When using a bundler like Vite or webpack, this will automatically benefit from [code splitting](https://webpack.js.org/guides/code-splitting/). ## Relationship to async components Vue Router's lazy loading may appear similar to Vue's [async components](https://vuejs.org/guide/components/async.html), but they are distinct features. Do **not** use async components as route components. An async component can still be used inside a route component but the route component itself should just be a function. ## Relationship to functional components While not common, it is possible to use a [functional component](https://vuejs.org/guide/extras/render-function.html#functional-components) as a route component. However, Vue Router needs some way to differentiate between functional components and lazy loading. To use a functional component we must give the function a `displayName`: ```ts const AboutPage: FunctionalComponent = () => { return h('h1', {}, 'About') } AboutPage.displayName = 'AboutPage' ``` ## Grouping Components in the Same Chunk We may want to group all the components nested under the same route into the same chunk, so they can all be loaded with a single request. ### With Vite We can define the chunks under the [`rollupOptions`](https://vite.dev/config/build-options.html#build-rollupoptions): ```js [vite.config.js] export default defineConfig({ build: { rollupOptions: { // https://rollupjs.org/guide/en/#outputmanualchunks output: { manualChunks: { 'group-user': [ './src/UserDetails', './src/UserDashboard', './src/UserProfileEdit', ], }, }, }, }, }) ``` ### With webpack We can specify the [chunk name](https://webpack.js.org/api/module-methods/#webpackchunkname) using a special comment syntax: ```js const UserDetails = () => import(/* webpackChunkName: "group-user" */ './UserDetails.vue') const UserDashboard = () => import(/* webpackChunkName: "group-user" */ './UserDashboard.vue') const UserProfileEdit = () => import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue') ``` webpack will group any async module with the same chunk name into the same async chunk. --- --- url: /data-loaders/organization.md --- # Loaders Organization While most examples show loaders defined in the same file as the page component, it's possible to define them in separate files and import them in the page component. This flexibility allows you to control not only the codebase organization but also **how chunks are split**. If a loader is used in multiple pages, it might be a better idea to extract it to a separate file instead of exporting it in one page and importing it in the others. This is because pages importing it will usually load the whole page component chunk in order to get the loader. ::: code-group ```ts [loaders/issues.ts] import { defineBasicLoader } from 'vue-router/experimental' import { getIssuesByProjectId } from '@/api' export const useProjectIssues = defineBasicLoader('/[projectId]/issues', to => getIssuesByProjectId(to.params.projectId) ) ``` ```vue{2-3,7} [pages/[projectId]/issues.vue] ``` ```vue{2,4,9} [pages/[projectId]/insights.vue] ``` ::: In the example above, the `useProjectIssues` loader is defined in a separate file and imported in two different pages, `pages/[projectId]/issues.vue` and `pages/[projectId]/insights.vue`. They both use the same data but present it in a different way so there is no reason to create two different loaders for issues. By extracting the loader into a separate file, we ensure an optimal chunk split. When using this pattern, remember to **export the loader** in all the page components that use it. This is what allows the router to await the loader before rendering the page. ## Usage outside of page components Until now, we have only seen loaders used in page components. However, one of the benefits of using loaders is that they can be **reused in many parts of your application**, just like regular composables. This will not only eliminate code duplication but also ensure an optimal and performant data fetching by **deduplicating requests and sharing the data**. To use a loader outside of a page component, you can simply **import it** and use it like any other composable, without the need to export it. ```vue ``` ::: tip When using a loader in a non-page component, you must **export the loader** from the page components where it is used. If you only import and use the loader in a regular component, the router will not recognize it and won't trigger or await it during navigation. ::: ## Nested Routes When defining nested routes, you don't need to worry about exporting the loader in both the parent and the child components. This will be automatically optimized for you and the loader will be shared between the parent and the child components. Because of this, it's simpler to **always export data loaders** in the page component where **they are used**. --- --- url: /guide/migration.md --- # Migrating from Vue 2 Most of Vue Router API has remained unchanged during its rewrite from v3 (for Vue 2) to v4 (for Vue 3) but there are still a few breaking changes that you might encounter while migrating your application. This guide is here to help you understand why these changes happened and how to adapt your application to make it work with Vue Router 4. ## Breaking Changes Changes are ordered by their usage. It is therefore recommended to follow this list in order. ### new Router becomes createRouter Vue Router is no longer a class but a set of functions. Instead of writing `new Router()`, you now have to call `createRouter`: ```js // previously was // import Router from 'vue-router' import { createRouter } from 'vue-router' const router = createRouter({ // ... }) ``` ### New `history` option to replace `mode` The `mode: 'history'` option has been replaced with a more flexible one named `history`. Depending on which mode you were using, you will have to replace it with the appropriate function: * `"history"`: `createWebHistory()` * `"hash"`: `createWebHashHistory()` * `"abstract"`: `createMemoryHistory()` Here is a full snippet: ```js import { createRouter, createWebHistory } from 'vue-router' // there is also createWebHashHistory and createMemoryHistory createRouter({ history: createWebHistory(), routes: [], }) ``` On SSR, you need to manually pass the appropriate history: ```js // router.js let history = isServer ? createMemoryHistory() : createWebHistory() let router = createRouter({ routes, history }) // somewhere in your server-entry.js router.push(req.url) // request url router.isReady().then(() => { // resolve the request }) ``` **Reason**: enable tree shaking of non used histories as well as implementing custom histories for advanced use cases like native solutions. ### Moved the `base` option The `base` option is now passed as the first argument to `createWebHistory` (and other histories): ```js import { createRouter, createWebHistory } from 'vue-router' createRouter({ history: createWebHistory('/base-directory/'), routes: [], }) ``` ### Removal of the `fallback` option The `fallback` option is no longer supported when creating the router: ```diff -new VueRouter({ +createRouter({ - fallback: false, // other options... }) ``` **Reason**: All browsers supported by Vue support the [HTML5 History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API), allowing us to avoid hacks around modifying `location.hash` and directly use `history.pushState()`. ### Removed `*` (star or catch all) routes Catch all routes (`*`, `/*`) must now be defined using a parameter with a custom regex: ```js const routes = [ // pathMatch is the name of the param, e.g., going to /not/found yields // { params: { pathMatch: ['not', 'found'] }} // this is thanks to the last *, meaning repeated params and it is necessary if you // plan on directly navigating to the not-found route using its name { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound }, // if you omit the last `*`, the `/` character in params will be encoded when resolving or pushing { path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound }, ] // bad example if using named routes: router.resolve({ name: 'bad-not-found', params: { pathMatch: 'not/found' }, }).href // '/not%2Ffound' // good example: router.resolve({ name: 'not-found', params: { pathMatch: ['not', 'found'] }, }).href // '/not/found' ``` :::tip You don't need to add the `*` for repeated params if you don't plan to directly push to the not found route using its name. If you call `router.push('/not/found/url')`, it will provide the right `pathMatch` param. ::: **Reason**: Vue Router doesn't use `path-to-regexp` anymore, instead it implements its own parsing system that allows route ranking and enables dynamic routing. Since we usually add one single catch-all route per project, there is no big benefit in supporting a special syntax for `*`. The encoding of params is encoding across routes, without exception to make things easier to predict. ### The `currentRoute` property is now a `ref()` Previously the properties of the [`currentRoute`](https://v3.router.vuejs.org/api/#router-currentroute) object on a router instance could be accessed directly. With the introduction of vue-router v4, the underlying type of the `currentRoute` object on the router instance has changed to `Ref`, which comes from the newer [reactivity fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html) introduced in Vue 3. While this doesn't change anything if you're reading the route with `useRoute()` or `this.$route`, if you're accessing it directly on the router instance, you will need to access the actual route object via `currentRoute.value`: ```ts const { page } = router.currentRoute.query // [!code --] const { page } = router.currentRoute.value.query // [!code ++] ``` ### Replaced `onReady` with `isReady` The existing `router.onReady()` function has been replaced with `router.isReady()` which doesn't take any argument and returns a Promise: ```js // replace router.onReady(onSuccess, onError) // with router.isReady().then(onSuccess).catch(onError) // or use await: try { await router.isReady() // onSuccess } catch (err) { // onError } ``` ### `scrollBehavior` changes The object returned in `scrollBehavior` is now similar to [`ScrollToOptions`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions): `x` is renamed to `left` and `y` is renamed to `top`. See [RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0035-router-scroll-position.md). **Reason**: making the object similar to `ScrollToOptions` to make it feel more familiar with native JS APIs and potentially enable future new options. ### ``, ``, and `` `transition` and `keep-alive` must now be used **inside** of `RouterView` via the `v-slot` API: ```vue-html ``` **Reason**: This was a necessary change. See the [related RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0034-router-view-keep-alive-transitions.md). ### Removal of `append` prop in `` The `append` prop has been removed from ``. You can manually concatenate the value to an existing `path` instead: ```vue-html replace to relative child with to relative child ``` You must define a global `append` function on your *App* instance: ```js app.config.globalProperties.append = (path, pathToAppend) => path + (path.endsWith('/') ? '' : '/') + pathToAppend ``` **Reason**: `append` wasn't used very often, is easy to replicate in user land. ### Removal of `event` and `tag` props in `` Both `event`, and `tag` props have been removed from ``. You can use the [`v-slot` API](/guide/advanced/composition-api#uselink) to fully customize ``: ```vue-html replace About Us with About Us ``` **Reason**: These props were often used together to use something different from an `` tag but were introduced before the `v-slot` API and are not used enough to justify adding to the bundle size for everybody. ### Removal of the `exact` prop in `` The `exact` prop has been removed because the caveat it was fixing is no longer present so you should be able to safely remove it. There are however two things you should be aware of: * Routes are now active based on the route records they represent instead of the generated route location objects and their `path`, `query`, and `hash` properties * Only the `path` section is matched, `query`, and `hash` aren't taken into account anymore If you wish to customize this behavior, e.g. take into account the `hash` section, you should use the [`v-slot` API](/guide/advanced/composition-api#useLink) to extend ``. **Reason**: See the [RFC about active matching](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0028-router-active-link.md#summary) changes for more details. ### Navigation guards in mixins are ignored At the moment navigation guards in mixins are not supported. You can track its support at [vue-router#454](https://github.com/vuejs/router/issues/454). ### Removal of `router.match` and changes to `router.resolve` Both `router.match`, and `router.resolve` have been merged together into `router.resolve` with a slightly different signature. [Refer to the API](/api/interfaces/RouterClassic.md#resolve-) for more details. **Reason**: Uniting multiple methods that were used for the same purpose. ### Removal of `router.getMatchedComponents()` The method `router.getMatchedComponents` is now removed as matched components can be retrieved from `router.currentRoute.value.matched`: ```js router.currentRoute.value.matched.flatMap(record => Object.values(record.components) ) ``` **Reason**: This method was only used during SSR and is a one liner that can be done by the user. ### Redirect records cannot use special paths Previously, a non documented feature allowed to set a redirect record to a special path like `/events/:id` and it would reuse an existing param `id`. This is no longer possible and there are two options: * Using the name of the route without the param: `redirect: { name: 'events' }`. Note this won't work if the param `:id` is optional * Using a function to recreate the new location based on the target: `redirect: to => ({ name: 'events', params: to.params })` **Reason**: This syntax was rarely used and *another way of doing things* that wasn't shorter enough compared to the versions above while introducing some complexity and making the router heavier. ### **All** navigations are now always asynchronous All navigations, including the first one, are now asynchronous, meaning that, if you use a `transition`, you may need to wait for the router to be *ready* before mounting the app: ```js app.use(router) // Note: on Server Side, you need to manually push the initial location router.isReady().then(() => app.mount('#app')) ``` Otherwise there will be an initial transition as if you provided the `appear` prop to `transition` because the router displays its initial location (nothing) and then displays the first location. Note that **if you have navigation guards upon the initial navigation**, you might not want to block the app render until they are resolved unless you are doing Server Side Rendering. In this scenario, not waiting the router to be ready to mount the app would yield the same result as in Vue 2. ### Removal of `router.app` `router.app` used to represent the last root component (Vue instance) that injected the router. Vue Router can now be safely used by multiple Vue applications at the same time. You can still add it when using the router: ```js app.use(router) router.app = app ``` You can also extend the TypeScript definition of the `Router` interface to add the `app` property. **Reason**: Vue 3 applications do not exist in Vue 2 and now we properly support multiple applications using the same Router instance, so having an `app` property would have been misleading because it would have been the application instead of the root instance. ### Passing content to route components' `` Before you could directly pass a template to be rendered by a route components' `` by nesting it under a `` component: ```vue-html

In Vue Router 3, I render inside the route component

``` Because of the introduction of the `v-slot` api for ``, you must pass it to the `` using the `v-slot` API: ```vue-html

In Vue Router 3, I render inside the route component

``` ### Removal of `parent` from route locations The `parent` property has been removed from normalized route locations (`this.$route` and object returned by `router.resolve`). You can still access it via the `matched` array: ```js const parent = this.$route.matched[this.$route.matched.length - 2] ``` **Reason**: Having `parent` and `children` creates unnecessary circular references while the properties could be retrieved already through `matched`. ### Removal of `pathToRegexpOptions` The `pathToRegexpOptions` and `caseSensitive` properties of route records have been replaced with `sensitive` and `strict` options for `createRouter()`. They can now also be directly passed when creating the router with `createRouter()`. Any other option specific to `path-to-regexp` has been removed as `path-to-regexp` is no longer used to parse paths. ### Removal of unnamed parameters Due to the removal of `path-to-regexp`, unnamed parameters are no longer supported: * `/foo(/foo)?/suffix` becomes `/foo/:_(foo)?/suffix` * `/foo(foo)?` becomes `/foo:_(foo)?` * `/foo/(.*)` becomes `/foo/:_(.*)` :::tip Note you can use any name instead of `_` for the param. The point is to provide one. ::: ### Usage of `history.state` Vue Router saves information on the `history.state`. If you have any code manually calling `history.pushState()`, you should likely avoid it or refactor it with a regular `router.push()` and a `history.replaceState()`: ```js // replace history.pushState(myState, '', url) // with await router.push(url) history.replaceState({ ...history.state, ...myState }, '') ``` Similarly, if you were calling `history.replaceState()` without preserving the current state, you will need to pass the current `history.state`: ```js // replace history.replaceState({}, '', url) // with history.replaceState(history.state, '', url) ``` **Reason**: We use the history state to save information about the navigation like the scroll position, previous location, etc. ### `routes` option is required in `options` The property `routes` is now required in `options`. ```js createRouter({ routes: [] }) ``` **Reason**: The router is designed to be created with routes even though you can add them later on. You need at least one route in most scenarios and this is written once per app in general. ### Non existent named routes Pushing or resolving a non existent named route throws an error: ```js // Oops, we made a typo in name router.push({ name: 'homee' }) // throws router.resolve({ name: 'homee' }) // throws ``` **Reason**: Previously, the router would navigate to `/` but display nothing (instead of the home page). Throwing an error makes more sense because we cannot produce a valid URL to navigate to. ### Missing required `params` on named routes Pushing or resolving a named route without its required params will throw an error: ```js // given the following route: const routes = [{ path: '/users/:id', name: 'user', component: UserDetails }] // Missing the `id` param will fail router.push({ name: 'user' }) router.resolve({ name: 'user' }) ``` **Reason**: Same as above. ### Named children routes with an empty `path` no longer appends a slash Given any nested named route with an empty `path`: ```js const routes = [ { path: '/dashboard', name: 'dashboard-parent', component: DashboardParent, children: [ { path: '', name: 'dashboard', component: DashboardDefault }, { path: 'settings', name: 'dashboard-settings', component: DashboardSettings, }, ], }, ] ``` Navigating or resolving to the named route `dashboard` will now produce a URL **without a trailing slash**: ```js router.resolve({ name: 'dashboard' }).href // '/dashboard' ``` This has an important side effect about children `redirect` records like these: ```js const routes = [ { path: '/parent', component: Parent, children: [ // this would now redirect to `/home` instead of `/parent/home` { path: '', redirect: 'home' }, { path: 'home', component: Home }, ], }, ] ``` Note this will work if `path` was `/parent/` as the relative location `home` to `/parent/` is indeed `/parent/home` but the relative location of `home` to `/parent` is `/home`. **Reason**: This is to make trailing slash behavior consistent: by default all routes allow a trailing slash. It can be disabled by using the `strict` option and manually appending (or not) a slash to the routes. ### `$route` properties Encoding Decoded values in `params`, `query`, and `hash` are now consistent no matter where the navigation is initiated (older browsers will still produce unencoded `path` and `fullPath`). The initial navigation should yield the same results as in-app navigations. Given any [normalized route location](/api/#RouteLocationNormalized): * Values in `path`, `fullPath` are not decoded anymore. They will appear as provided by the browser (most browsers provide them encoded). e.g. directly writing on the address bar `https://example.com/hello world` will yield the encoded version: `https://example.com/hello%20world` and both `path` and `fullPath` will be `/hello%20world`. * `hash` is now decoded, that way it can be copied over: `router.push({ hash: $route.hash })` and be used directly in [scrollBehavior](/api/interfaces/RouterOptions.md#scrollBehavior)'s `el` option. * When using `push`, `resolve`, and `replace` and providing a `string` location or a `path` property in an object, **it must be encoded** (like in the previous version). On the other hand, `params`, `query` and `hash` must be provided in its unencoded version. * The slash character (`/`) is now properly decoded inside `params` while still producing an encoded version on the URL: `%2F`. The same applies to static route record paths that contain characters requiring escaping: ```js const routes = [{ path: '/hello%20world', component: HelloWorld }] ``` When manually building a string or object `path`, encode dynamic segments yourself. Prefer named routes with `params` when possible: ```js const username = 'eduardo/san martin' router.push(`/user/${encodeURIComponent(username)}`) router.push({ path: `/user/${encodeURIComponent(username)}` }) router.push({ name: 'user', params: { username } }) ``` **Reason**: This allows to easily copy existing properties of a location when calling `router.push()` and `router.resolve()`, and make the resulting route location consistent across browsers. `router.push()` is now idempotent, meaning that calling `router.push(route.fullPath)`, `router.push({ hash: route.hash })`, `router.push({ query: route.query })`, and `router.push({ params: route.params })` will not create extra encoding. ### `$router.push()` and `$router.replace()` - `onComplete` and `onAbort` callbacks Previously, `$router.push()` and `$router.replace()` accepted two callbacks, `onComplete` and `onAbort`, as second and third arguments. They were called after a navigation based on the result. With the introduction of a Promise based API, these callbacks are redundant and have been removed. See [Navigation Failures](/guide/advanced/navigation-failures.md) for more information on how to detect successful and failed navigations. **Reason**: Reduce library size by adapting to established JS standards (Promises). ### TypeScript changes To make typings more consistent and expressive, some types have been renamed: | `vue-router@3` | `vue-router@4` | | -------------- | ----------------------- | | RouteConfig | RouteRecordRaw | | Location | RouteLocation | | Route | RouteLocationNormalized | ## New Features Some of new features to keep an eye on in Vue Router 4 include: * [Dynamic Routing](../advanced/dynamic-routing.md) * [Composition API](../advanced/composition-api.md) --- --- url: /guide/migration/v4-to-v5.md --- # Migrating to Vue Router 5 > \[!TIP] > Vue Router 5 is a transition release that merges [unplugin-vue-router](https://uvr.esm.is) (file-based routing) into the core package. **If you're using Vue Router 4 without unplugin-vue-router, there are no breaking changes** - you can upgrade without any code modifications. The only exception is that the *iife* build no longer includes `@vue/devtools-api` because it has been upgraded to v8 and does not expose an IIFE build itself. You can track that change in [this issue](https://github.com/vuejs/devtools/issues/989). > > Vue Router 6 will be ESM-only and remove deprecated APIs. v5 gives you time to prepare for that transition. ## For Vue Router 4 Users (without file-based routing) No breaking changes. Update your dependency and you're done: ```bash pnpm update vue-router@5 ``` ## From unplugin-vue-router If you were using unplugin-vue-router for file-based routing, migration is mostly import path changes. ### Migration Checklist (TLDR) ### 1. Update Dependencies ```bash pnpm remove unplugin-vue-router pnpm update vue-router@5 ``` ### 2. Update Imports **Vite plugin:** ```ts import VueRouter from 'unplugin-vue-router/vite' // [!code --] import VueRouter from 'vue-router/vite' // [!code ++] ``` Other build tools (Webpack, Rollup, esbuild) import from `vue-router/unplugin`: ```ts import VueRouter from 'vue-router/unplugin' VueRouter.webpack({/* ... */}) VueRouter.rollup({/* ... */}) // etc. ``` **Data loaders:** ```ts import { defineBasicLoader } from 'unplugin-vue-router/data-loaders/basic' // [!code --] import { defineColadaLoader } from 'unplugin-vue-router/data-loaders/pinia-colada' // [!code --] import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders' // [!code --] import { defineBasicLoader, DataLoaderPlugin } from 'vue-router/experimental' // [!code ++] import { defineColadaLoader } from 'vue-router/experimental/pinia-colada' // [!code ++] ``` **Unplugin utilities (for custom integrations):** ```ts import { VueRouterAutoImports, EditableTreeNode, createTreeNodeValue, createRoutesContext, getFileBasedRouteName, getPascalCaseRouteName, } from 'unplugin-vue-router' // [!code --] } from 'vue-router/unplugin' // [!code ++] ``` **Types:** ```ts import type { Options, EditableTreeNode } from 'unplugin-vue-router' // [!code --] import type { Options, EditableTreeNode } from 'vue-router/unplugin' // [!code ++] ``` **Volar plugins:** ```jsonc // tsconfig.json { "compilerOptions": { "rootDir": ".", }, "vueCompilerOptions": { "plugins": [ "unplugin-vue-router/volar/sfc-typed-router", // [!code --] "unplugin-vue-router/volar/sfc-route-blocks", // [!code --] "vue-router/volar/sfc-typed-router", // [!code ++] "vue-router/volar/sfc-route-blocks", // [!code ++] ], }, } ``` ### 3. Update vite.config.ts and tsconfig.json It's recommended to move the generated types file inside `src/` and rename it to `route-map.d.ts`, as it's automatically included by most setups: ```ts // vite.config.ts export default defineConfig({ plugins: [ VueRouter({ dts: 'src/route-map.d.ts', // [!code ++] }), Vue(), ], }) ``` Remove the old client types reference. These were either added to an `env.d.ts`: ```ts /// // [!code --] ``` or to your `tsconfig.json`: ```jsonc { "include": [ "./typed-router.d.ts", // [!code --] "unplugin-vue-router/client", // [!code --] // ... ], } ``` ## Troubleshooting **Types not recognized:** Restart your TypeScript server and check that your generated types file (e.g., `src/route-map.d.ts`) is included in your tsconfig. **Routes not generating:** Verify your `routesFolder` path and check file extensions. **Route name errors:** Use the generated names or add `definePage({ name: 'custom-name' })` to your components. ## New Exports Reference | Export | Purpose | | -------------------------------------- | ---------------------------------- | | `vue-router` | Main API (unchanged) | | `vue-router/vite` | Vite plugin | | `vue-router/auto-routes` | Generated routes | | `vue-router/unplugin` | Webpack/Rollup/esbuild + utilities | | `vue-router/experimental` | Data loaders | | `vue-router/experimental/pinia-colada` | Pinia Colada loader | --- --- url: /guide/essentials/named-routes.md --- # Named Routes When creating a route, we can optionally give the route a `name`: ```js const routes = [ { path: '/user/:username', name: 'profile', // [!code highlight] component: User, }, ] ``` We can then use the `name` instead of the `path` when passing the `to` prop to ``: ```vue-html User profile ``` The example above would create a link to `/user/erina`. * [See it in the Playground](https://play.vuejs.org/#eNqtVVtP2zAU/itWNqlFauNNIB6iUMEQEps0NjH2tOzBtKY1JLZlO6VTlP++4+PcelnFwyRofe7fubaKCiZk/GyjJBKFVsaRiswNZ45faU1q8mRUQUbrko8yuaPwlRfK/LkV1sHXpGHeq9JxMzScGmT19t5xkMaUaR1vOb9VBe+kntgWXz2Cs06O1LbCTwvRW7knGnEm50paRwIYcrEFd1xlkpBVyCQ5lN74ZOJV0Nom5JcnCFRCM7dKyIiOJkSygsNzBZiBmivAI7l0SUipRvuhCfPge7uWHBiGZPctS0iLJv7T2/YutFFPIt+JjgUJPn7DZ32CtWg7PIZ/4BASg7txKE6gC1VKNx69gw6NTqJJ1HQK5iR1vNA52M+8Yrr6OLuD+AuCtbQpBQYK9Oy6NAZAhLI1KKuKvEc69jSp65Tqw/oh3V7f00P9MsdveOWiecE75DDNhXwhiVMXWVRttYbUWdRpE2xOZ0sHxq1v2jl/a5jQyZ042Mv/HKjvt2aGFTCXFWmnAsTcCMkAxw4SHIjG9E2AUtpUusWyFvyVUGCltBsFmJB2W/dHZCHWswdYLwJ/XiulnrNr323zcQeodthDuAHTgmm4aEqCH1zsrBHYLIISheyyqD9Nnp1FK+e0TSgtpX5ZxrBBtNe4PItP4w8Q07oBN+a2mD4a9erPzDN4bzY1iy5BiS742imV2ynT4l8h9hQvz+Pz+COU/pGCdyrkgm/Qt3ddw/5Cms7CLXsSy50k/dJDT8037QTcuq1kWZ6r1y/Ic6bkHdD5is9fDvCf7SZA/m44ZLfmg+QcM0vugvjmxx3fwLsTFmpRwlwdE95zq/LSYwxqn0q5ANgDPUT7GXsm5PLB3mwcl7ZNygPFaqA+NvL6SOo93NP4bFDF9sfh+LThtgxvkF80fyxxy/Ac7U9i/RcYNWrd). Using a `name` has various advantages: * No hardcoded URLs. * Automatic encoding of `params`. * Avoids URL typos. * Bypassing path ranking, e.g. to display a lower-ranked route that matches the same path. Each name **must be unique** across all routes. If you add the same name to multiple routes, the router will only keep the last one. You can read more about this [in the Dynamic Routing](../advanced/dynamic-routing#Removing-routes) section. There are various other parts of Vue Router that can be passed a location, e.g. the methods `router.push()` and `router.replace()`. We'll go into more detail about those methods in the guide to [programmatic navigation](./navigation). Just like the `to` prop, these methods also support passing a location by `name`: ```js router.push({ name: 'profile', params: { username: 'erina' } }) ``` --- --- url: /guide/essentials/named-views.md --- # Named Views Sometimes you need to display multiple views at the same time instead of nesting them, e.g. creating a layout with a `sidebar` view and a `main` view. This is where named views come in handy. Instead of having one single outlet in your view, you can have multiple and give each of them a name. A `router-view` without a name will be given `default` as its name. ```vue-html ``` A view is rendered by using a component, therefore multiple views require multiple components for the same route. Make sure to use the `components` (with an **s**) option: ```js const router = createRouter({ // ... routes: [ { path: '/', components: { default: Home, // Renders to sidebar: MainSidebar, // Renders to footer: MainToolbar, // Renders to }, }, ], }) ``` * [See it in the Playground](https://play.vuejs.org/#eNq1Vm1v2zYQ/iuEMsAOZkt29oJBU410RbFuWLuiKfql6gdaomzVEimQlOPA8H/vkZRoSmbSfKkRx9Ld8xyPx4dHHoMalzT8KoI4KOuGcYmOKOMES/KyadAJFZzVaLJvySSlFsBZKwm33jAyhjNEcXsfbprQ8FNqI0/he51ShMJWkKmhm/eatVROJ1dAm1wHs6CjQ36JJHVTAX+lgMl2uXqHa5KjTyW5F0kE79re6B94EJIzulm9ajknVJqkUYPlNk6izoeOR/STdoRFW1XvwYlOJx0mMnESivd9wA86zf9KukOSvUiDKA1WfzN4Rm9YTZLo7H+UgddgsbSX6u1ZPEGkLOlGWOpdZ7hgJ1GfcZKXe5RVWAiIkDEqoQrA74bAosxNJfWrWYL5HmqJKJQVKAqwxjwNUNSTIpeVKOn4I5wJFpNEkI55KhgDZA/xjGwAduAksowkckQA6jBkr37fkprxhzelkPAz64ymWK6u51a7Vr1qNZWorIR7Q6fjDvYWpnZnamSRju0S/JGxagzubEOw1sUgA2sZAnsVDLCu0Q8fZz2y+0nj7Ed2u8fJQdNAcKLbdBy9GJR/elSrujUrE/uWa3o9UxDNFjH6bKSiaeqjNzGaRBONUp+MQbIUBA5oC0MoJwVuKxnbJbUEhDp5x+46Om4jOePtpmi9p+7pNLvMSe/w5ydmF/bHZ9b3kOcn5yrJl99IN54cRyLx5Kl+v8D/E7R/2NDuVvP0/JtVD4CGfzNsCMAebT9vAAfjjWFkP69xA22FUYihS5N2DmjBtlhpcG4gypwGWykbEUdRS5vdJoT6RmfE7W/hIvwd2qCQjjUkop6vObsXuo+lQVeZNLgFUJSTvYTaiTluyseGuADe/hEuw+V5JNd3MZ4aDo68E0xdCti1RbkZTVzJpKwI/7+RJezqQQFwVbH7f7VN8pbY5LMtyXYe+1dxMNN4zwlksCfOhCXmGwJHlHK/vntHDvBsnTXL2wrQTzg/EMGqVuVoYH+1NIe0HZzO9h+9jqDKj+L1AQ5F0U9KJaqrofF6cV89MfVzur+EvzpVHLX1R0XYYbwiHPR7bwSL8PLHZ4A3hAt6Msr3ttQI92Ss71VmhPPGEvKhIiLMhDr2YQvoq41ZGNA8wOBUKSpy+BMWRB1JFqJuIwan3DFaAgKBlW9KOq9IAT1vuWgsD25SM6SvPDNNnXV9zYRYM56rHnfTHBAIr8zR1WKxcCOumZSsvohp6A3Oc5go8EfeFcLoZ/hq1CA3F7m9MQAM5zRW4gS3gEN1s63gK500YmSS8g54uyMPBYdrl8PtKqTO+U7sa5ztNtCvaD7PWMVg0ldFYaLq2zJcSB9FSo6paLC6g/cE+AtO3wDyWC1R) ## Nested Named Views It is possible to create complex layouts using named views with nested views. When doing so, you will also need to give nested `router-view` a name. Let's take a Settings panel example: ``` /settings/emails /settings/profile +-----------------------------------+ +------------------------------+ | UserSettings | | UserSettings | | +-----+-------------------------+ | | +-----+--------------------+ | | | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | | | | +-------------------------+ | | | +--------------------+ | | | | | | | | | UserProfilePreview | | | +-----+-------------------------+ | | +-----+--------------------+ | +-----------------------------------+ +------------------------------+ ``` * `Nav` is just a regular component * `UserSettings` is the parent view component * `UserEmailsSubscriptions`, `UserProfile`, `UserProfilePreview` are nested view components **Note**: *Let's forget about how the HTML/CSS should look like to represent such layout and focus on the components used.* The `