Navigation Guards
As the name suggests, the navigation guards provided by Vue router are primarily used to guard navigations either by redirecting it or canceling it. There are a number of ways to hook into the route navigation process: globally, per-route, or in-component.
Global Before Guards
You can register global before guards using router.beforeEach
:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 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 two arguments:
to
: the target route location in a normalized format being navigated to.from
: the current route location in a normalized format being navigated away from.
And can optionally return any of the following values:
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 thefrom
route.A Route Location: Redirect to a different location by passing a route location as if you were calling
router.push()
, which allows you to pass options likereplace: true
orname: 'home'
. The current navigation is dropped and a new one is created with the samefrom
.jsrouter.beforeEach(async (to, from) => { if ( // make sure the user is authenticated !isAuthenticated && // ❗️ Avoid an infinite redirect to.name !== 'Login' ) { // redirect the user to the login page return { name: 'Login' } } })
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()
.
If nothing, undefined
or true
is returned, the navigation is validated, and the next navigation guard is called.
All of the things above work the same way with async
functions and Promises:
router.beforeEach(async (to, from) => {
// canUserAccess() returns `true` or `false`
const canAccess = await canUserAccess(to)
if (!canAccess) return '/login'
})
Optional third argument next
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 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 the user to /login
if they are not authenticated:
// BAD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
// if the user is not authenticated, `next` is called twice
next()
})
Here is the correct version:
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else 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. Here is an example that ensures the user has given access to the Camera for routes that have defined a custom meta property requiresCamera
:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... handle the error and then cancel the navigation
return false
} else {
// unexpected error, cancel the navigation and pass the error to the global handler
throw error
}
}
}
})
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.
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:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
They are useful for analytics, changing the title of the page, accessibility features like announcing the page and many other things.
They also reflect navigation failures as the third argument:
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
Learn more about navigation failures on its guide.
Global injections within guards
Since Vue 3.3, it is possible to use inject()
within navigation guards. This is useful for injecting global properties like the pinia stores. Anything that is provided with app.provide()
is also accessible within router.beforeEach()
, router.beforeResolve()
, router.afterEach()
:
// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')
// router.ts or main.ts
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// a pinia store
const userStore = useAuthStore()
// ...
})
Per-Route Guard
You can define beforeEnter
guards directly on a route's configuration object:
const routes = [
{
path: '/users/:id',
component: UserDetails,
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:
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],
},
]
When working with nested routes, both parent and child routes can use beforeEnter
. When placed on a parent route, it won't be triggered when moving between children with that same parent. For example:
const routes = [
{
path: '/user',
beforeEnter() {
// ...
},
children: [
{ path: 'list', component: UserList },
{ path: 'details', component: UserDetails },
],
},
]
The beforeEnter
in the example above won't be called when moving between /user/list
and /user/details
, as they share the same parent. If we put the beforeEnter
guard directly on the details
route instead, that would be called when moving between those two routes.
TIP
It is possible to achieve similar behavior to per-route guards by using route meta fields and global navigation guards.
In-Component Guards
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
beforeRouteLeave
<script>
export default {
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) {
// 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 navigate between `/users/1` and `/users/2`,
// the same `UserDetails` component instance 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) {
// 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.
},
}
</script>
The beforeRouteEnter
guard does NOT have access to this
, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next
. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component public instance via `vm`
})
}
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:
beforeRouteUpdate (to, from) {
// just use `this`
this.name = to.params.name
}
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
.
beforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}
Using the Composition API
If you are writing your component using the Composition API, you can add update and leave guards through onBeforeRouteUpdate
and onBeforeRouteLeave
respectively. Please refer to the Composition API section for more details.
The Full Navigation Resolution Flow
- Navigation triggered.
- Call
beforeRouteLeave
guards in deactivated components. - Call global
beforeEach
guards. - Call
beforeRouteUpdate
guards in reused components. - Call
beforeEnter
in route configs. - Resolve async route components.
- Call
beforeRouteEnter
in activated components. - Call global
beforeResolve
guards. - Navigation is confirmed.
- Call global
afterEach
hooks. - DOM updates triggered.
- Call callbacks passed to
next
inbeforeRouteEnter
guards with instantiated instances.