```js
const router = createRouter({ ... })
-router.beforeEach((to, from, next) => {
+router.beforeEach((to, from) => {
// ...
- next()
+ // explicitly return false to cancel the navigation
+ return false
})
```
Global before guards are called in creation order, whenever a navigation is triggered. Guards may be resolved asynchronously, and the navigation is considered **pending** before all hooks have been resolved.
-Every guard function receives three arguments:
+Every guard function receives two arguments:
-- **`to`**: the target [Normalized RouteLocation](../../api/#the-route-object) being navigated to.
+- **`to`**: the target route location [in a normalized format](../../api/#the-route-object) being navigated to.
+- **`from`**: the current route location [in a normalized format](../../api/#the-route-object) being navigated away from.
-- **`from`**: the current route location (same type as `to`) being navigated away from.
+And can optionally return any of the following values:
-- **`next`**: a function that **must be called to resolve** the hook. The action depends on the arguments provided to `next`:
+- `false`: cancel the current navigation. If the browser URL was changed (either manually by the user or via back button), it will be reset to that of the `from` route.
+- A [Route Location](../api#route-location): Redirect to a different location by passing a route location as if you were calling [`router.push()`](../api#router-push), which allows you to pass options like `replace: true` or `name: 'home'`. The current navigation is dropped and a new one is created with the same `from`.
- - **`next()`**: move on to the next hook in the pipeline. If no hooks are left, the navigation is **validated**.
+It's also possible to throw an `Error` if an unexpected situation was met. This will also cancel the navigation and call any callback registered via [`router.onError()`](../api#router-onerror).
- - **`next(false)`**: cancel the current navigation. If the browser URL was changed (either manually by the user or via back button), it will be reset to that of the `from` route.
+If nothing, `undefined` or `true` is returned, **the navigation is validated**, and the next navigation guard is called.
- - **`next('/')` or `next({ path: '/' })`**: redirect to a different location. The current navigation will be dropped and a new one will be initiated. **You can pass any route location object** to `next`, which allows you to specify options like `replace: true`, `name: 'home'` and any option used in [`router-link`'s `to` prop](../../api/#to) or [`router.push`](../../api/#router-push)
+All of the the things above **work the same way with `async` functions** and Promises:
- - **`next(error)`**: (2.4.0+) if the argument passed to `next` is an instance of `Error`, the navigation will be canceled and the error will be passed to callbacks registered via [`router.onError()`](../../api/#router-onerror). The same happens if an error is directly thrown.
+```js
+router.beforeEach(async (to, from) => {
+ // canUserAccess() returns `true` or `false`
+ return await canUserAccess(to)
+})
+```
+
+### Optional third argument `next`
-::: tip Note
-For all guards, **make sure that the `next` function is called exactly once** in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors. Here is an example of redirecting to user to `/login` if they are not authenticated:
+In previous versions of Vue Router, it was also possible to use a _third argument_ `next`, this was a common source of mistakes and went through an [RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0037-router-return-guards.md#motivation) to remove it. However, it is still supported, meaning you can pass a third argument to any navigation guard. In that case, **you must call `next` exactly once** in any given pass through a navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors. Here is **a bad example** of redirecting to user to `/login` if they are not authenticated:
```js
// BAD
})
```
+Here is the correct version:
+
```js
// GOOD
router.beforeEach((to, from, next) => {
})
```
-:::
-
## Global Resolve Guards
-You can register a global guard with `router.beforeResolve`. This is similar to `router.beforeEach` because it triggers on **every navigation**, but resolve guards are called right before the navigation is confirmed, **after all in-component guards and async route components are resolved**. This is the ideal spot to fetch data or do any other operation that you want to avoid doing if the user cannot enter a page. It's also very easy to combine with [`meta` fields](./meta.md) to create a [generic fetching mechanism](../../cookbook/generic-data-fetching.md):
+You can register a global guard with `router.beforeResolve`. This is similar to `router.beforeEach` because it triggers on **every navigation**, but resolve guards are called right before the navigation is confirmed, **after all in-component guards and async route components are resolved**. Here is an example that ensures the user has given access to the Camera for routes that [have defined a custom meta](#TODO) property `requiresCamera`:
```js
-router.beforeResolve(async (to, from, next) => {
+router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
- next()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... handle the error and then cancel the navigation
- next(false)
+ return false
} else {
// unexpected error, cancel the navigation and pass the error to the global handler
- next(error)
+ throw error
}
}
- } else {
- // make sure to always call `next`
- next()
}
})
```
+`router.beforeResolve` is the ideal spot to fetch data or do any other operation that you want to avoid doing if the user cannot enter a page. It's also very easy to combine with [`meta` fields](./meta.md) to create a [generic fetching mechanism](../../cookbook/generic-data-fetching.md)
+
## Global After Hooks
You can also register global after hooks, however unlike guards, these hooks do not get a `next` function and cannot affect the navigation:
They are useful for analytics, [changing the title of the page](../../cookbook/page-title.md), [accessibility](../../cookbook/announcing-navigation.md) and many other things.
+They also reflect [navigation failures](./navigation-failures.md) as the third argument:
+
+```js
+router.afterEach((to, from, failure) => {
+ if (!failure) sendToAnalytics(to.fullPath)
+})
+```
+
+Learn more about navigation failures on [its guide](./navigation-failures.md).
+
## Per-Route Guard
You can define `beforeEnter` guards directly on a route's configuration object:
{
path: '/users/:id',
component: UserDetails,
- beforeEnter: (to, from, next) => {
- // ...
- next()
- }
- }
+ beforeEnter: (to, from) => {
+ // reject the navigation
+ return false
+ },
+ },
]
```
`beforeEnter` guards **only trigger when entering the route**, they don't trigger when the `params`, `query` or `hash` change e.g. going from `/users/2` to `/users/3` or going from `/users/2#info` to `/users/2#projects`. They are only triggered when navigating **from a different** route.
+You can also pass an array of functions to `beforeEnter`, this is useful when reusing guards for different routes:
+
+```js
+function removeQueryParams(to) {
+ if (Object.keys(to.query).length)
+ return { path: to.path, query: {}, hash: to.hash }
+}
+
+function removeHash(to) {
+ if (to.hash) return { path: to.path, query: to.query, hash: '' }
+}
+
+const routes = [
+ {
+ path: '/users/:id',
+ component: UserDetails,
+ beforeEnter: [removeQueryParams, removeHash],
+ },
+ {
+ path: '/about',
+ component: UserDetails,
+ beforeEnter: [removeQueryParams],
+ },
+]
+```
+
+Note it is possible to achieve a similar behavior by using [route meta fields](./meta.md) and [global navigation guards](#global-before-guards).
+
## In-Component Guards
-Finally, you can directly define route navigation guards inside route components (the ones passed to the router configuration) with the following options:
+Finally, you can directly define route navigation guards inside route components (the ones passed to the router configuration)
+
+### Using the options API
+
+You can add the following options to route components:
- `beforeRouteEnter`
- `beforeRouteUpdate`
```js
const UserDetails = {
template: `...`,
- beforeRouteEnter(to, from, next) {
+ beforeRouteEnter(to, from) {
// called before the route that renders this component is confirmed.
// does NOT have access to `this` component instance,
// because it has not been created yet when this guard is called!
},
- beforeRouteUpdate(to, from, next) {
+ beforeRouteUpdate(to, from) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, given a route with params `/users/:id`, when we
// will be reused, and this hook will be called when that happens.
// Because the component is mounted while this happens, the navigation guard has access to `this` component instance.
},
- beforeRouteLeave(to, from, next) {
+ beforeRouteLeave(to, from) {
// called when the route that renders this component is about to
// be navigated away from.
// As with `beforeRouteUpdate`, it has access to `this` component instance.
- }
+ },
}
```
Note that `beforeRouteEnter` is the only guard that supports passing a callback to `next`. For `beforeRouteUpdate` and `beforeRouteLeave`, `this` is already available, so passing a callback is unnecessary and therefore _not supported_:
```js
-beforeRouteUpdate (to, from, next) {
+beforeRouteUpdate (to, from) {
// just use `this`
this.name = to.params.name
- next()
}
```
-The **leave guard** is usually used to prevent the user from accidentally leaving the route with unsaved edits. The navigation can be canceled by calling `next(false)`.
+The **leave guard** is usually used to prevent the user from accidentally leaving the route with unsaved edits. The navigation can be canceled by returning `false`.
```js
-beforeRouteLeave (to, from, next) {
+beforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
- if (answer) {
- next()
- } else {
- next(false)
- }
+ if (!answer) return false
+}
+```
+
+### Using the composition API
+
+If you are writing your component using the [composition API and a `setup` function](https://v3.vuejs.org/guide/composition-api-setup.html#setup), you can add update and leave guards:
+
+```js
+import { onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
+
+const UserDetails = {
+ template: `...`,
+ setup() {
+ onBeforeRouteUpdate((to, from) => {
+ // same as the beforeRouteUpdate option but with no access to `this`
+ })
+
+ onBeforeRouteLeave((to, from) => {
+ // same as the beforeRouteLeave option but with no access to `this`
+ })
+ },
}
```