Overview
Vue Router provides navigation guards that enable developers to execute custom logic during route transitions. These hooks activate at critical moments in the navigation process, making them essential for implementing features like access control, data preloading, and route validation.
Navigation guards in Vue Router essentially serve as interceptors for monitoring route entry, updates, and exits.
Guard Parameters
Each guard receives three parameters:
- to: The target route object being navigated to
- from: The current route being navigated away from
- next: A required function to resolve the guard and continue navigation
About the next() Function
The next() method is critical - navigation will not proceed unless it is called. Here are the available options:
- next(): Proceeds to the next guard in the pipeline. If all guards pass, navigation becomes confirmed.
- next(false): Cancels the current navigation. If the URL changed, it reverts to the from route.
- next('/path') or next({ path: '/path' }): Redirects to a different location, aborting current navigation.
- next(error): (2.4.0+) If an Error isntance is passed, navigation terminates and the error routes to router.onError().
Types of Navigation Guards
Vue Router supports three categories of navigation guards:
- Global Guards: beforeEach, beforeResolve, afterEach
- Per-Route Guards: beforeEnter
- Component Guards: beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave
1. Global Guards
Global guards apply to the entire router instance. There are three main global guards:
beforeEach - Global Before Guard
Executes before every route navigation. Common use cases include authentication verification and redirecting unauthenticated users.
const router = createRouter({
history: createWebHistory(),
routes: [
// route definitions
]
});
router.beforeEach((to, from, next) => {
// Check authentication status
const isAuthenticated = localStorage.getItem('authToken');
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
next();
}
});
beforeResolve - Global Resolve Guard
Introduced in version 2.5.0+, this guard executes after beforeRouteEnter but before navigation is confirmed. Ideal for prefetching data required by the target component.
router.beforeResolve(async (to, from, next) => {
// Preload critical data before navigation completes
const requiredData = await fetchComponentData(to.params.id);
if (requiredData) {
to.meta.preloadedData = requiredData;
next();
} else {
next({ name: 'Error', params: { message: 'Data unavailable' } });
}
});
afterEach - Global After Guard
Executes after navigation completes, regardless of success or failure. Suitable for cleanup tasks, analytics tracking, or scroll position management.
router.afterEach((to, from) => {
// Reset scroll position on route change
window.scrollTo(0, 0);
// Update page title dynamically
document.title = to.meta.pageTitle || 'Default Title';
});
2. Per-Route Guards
Route-specific guards can be defined directly within route configurations using beforeEnter. These guards only trigger when accessing that particular route.
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/admin',
component: AdminDashboard,
beforeEnter: (to, from, next) => {
// Verify admin privileges
const userRole = getUserRole();
if (userRole === 'admin') {
next();
} else {
next({ name: 'Forbidden' });
}
}
}
]
});
3. Component Guards
Component-specific guards are defined within Vue components and provide fine-grained control over component-level navigation logic.
beforeRouteEnter
Executes before the component is mounted. At this point, the component instance is not yet created, so this is unavailable. Access the component instance through the next callback.
export default {
data() {
return {
userProfile: null
};
},
beforeRouteEnter(to, from, next) {
// Component instance not available here
fetchUserData(to.params.userId)
.then(data => {
next(vm => {
// Access component instance via vm
vm.userProfile = data;
});
})
.catch(error => {
next({ name: 'Error' });
});
}
};
beforeRouteUpdate
Executes when a route with dynamic parameters changes but the same component is reused. For example, navigating from /user/1 to /user/2 with a User component.
export default {
data() {
return {
currentUser: null
};
},
beforeRouteUpdate(to, from, next) {
// Component instance available via 'this'
this.fetchUserData(to.params.userId)
.then(data => {
this.currentUser = data;
next();
});
}
};
beforeRouteLeave
Executes when navigating away from the current route. Commonly used for preventing accidental navigation with unsaved changes.
export default {
data() {
return {
hasUnsavedChanges: false
};
},
beforeRouteLeave(to, from, next) {
if (this.hasUnsavedChanges) {
const confirmLeave = window.confirm('You have unsaved changes. Leave anyway?');
if (confirmLeave) {
next();
} else {
next(false);
}
} else {
next();
}
}
};
Vue 3 Composition API Usage
With Vue Router 4 and Composition API, use composable functions for navigation guards:
import {
onBeforeRouteEnter,
onBeforeRouteUpdate,
onBeforeRouteLeave
} from 'vue-router';
export default {
setup() {
onBeforeRouteEnter((to, from, next) => {
next(vm => {
// Access component instance
});
});
onBeforeRouteUpdate((to, from, next) => {
// Handle parameter changes
next();
});
onBeforeRouteLeave((to, from, next) => {
// Cleanup before leaving
next();
});
return {};
}
};
Version Comparison
| Vue Router 3 (Vue 2) | Vue Router 4 (Vue 3) |
|---|---|
| beforeRouteEnter | onBeforeRouteEnter |
| beforeRouteUpdate | onBeforeRouteUpdate |
| beforeRouteLeave | onBeforeRouteLeave |
Guard Execution Order
Understanding the execution sequence is critical for proper guard implementation. The complete order is:
- Global beforeEach: Executes first on every navigation
- Per-route beforeEnter: Executes for the target route if defined
- Global beforeResolve: Executes after all beforeRouteEnter guards
- Component beforeRouteEnter: Executes before component instantiation
- Global afterEach: Executes after navigation confirms
- Component beforeRouteUpdate: Executes when route changes but component reuses
- Component beforeRouteLeave: Executes when navigating away
Route Authentication Implementation
Route authentication is a critical feature for protecting sensitive routes and controlling user access.
Step 1: Define Route Metadata
Add metadata to routes to indicate authentication requirements:
const routes = [
{
path: '/login',
name: 'Login',
component: LoginPage,
meta: {
requiresAuth: false
}
},
{
path: '/settings',
name: 'Settings',
component: SettingsPage,
meta: {
requiresAuth: true,
allowedRoles: ['user', 'admin']
}
}
];
Step 2: Implement Authentication Guard
Use global beforeEach to verify authentication before navigation:
router.beforeEach((to, from, next) => {
const requiresAuth = to.meta.requiresAuth;
const allowedRoles = to.meta.allowedRoles || [];
if (requiresAuth) {
const storedUser = JSON.parse(localStorage.getItem('userData'));
if (!storedUser || !storedUser.token) {
next({
name: 'Login',
query: { redirect: to.fullPath }
});
return;
}
// Validate token expiration
const currentTime = Date.now();
if (storedUser.expiresAt < currentTime) {
localStorage.removeItem('userData');
next({
name: 'Login',
query: { redirect: to.fullPath }
});
return;
}
// Check role-based access
if (allowedRoles.length > 0 && !allowedRoles.includes(storedUser.role)) {
next({ name: 'Forbidden' });
return;
}
}
next();
});
Complete Router Configuration
import { createRouter, createWebHistory } from 'vue-router';
let savedScrollPosition = 0;
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
pageTitle: 'Dashboard'
}
},
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: {
requiresAuth: true,
pageTitle: 'User Profile'
}
}
]
});
// Authentication guard
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
const userData = JSON.parse(localStorage.getItem('userData'));
if (!userData?.token) {
next({ name: 'Login', query: { redirect: to.fullPath } });
return;
}
if (userData.expiresAt < Date.now()) {
localStorage.removeItem('userData');
next({ name: 'Login', query: { redirect: to.fullPath } });
return;
}
}
// Save scroll position before navigating away
if (from.path !== '/') {
savedScrollPosition = window.scrollY;
}
next();
});
// Navigation complete handler
router.afterEach((to) => {
if (to.path !== '/') {
window.scrollTo(0, 0);
} else {
// Restore scroll position on back navigation
requestAnimationFrame(() => {
window.scrollTo(0, savedScrollPosition);
});
}
});
export default router;
Error Handling
Vue Router 4 (Vue 3)
Use router.onError to register error callbacks:
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
// route configurations
]
});
router.onError((error) => {
console.error('Navigation error:', error);
// Display error page or notification
showErrorNotification('Navigation failed. Please try again.');
});
export default router;
Vue Router 3 (Vue 2)
In Vue Router 3, detect errors through the afterEach guard:
router.afterEach((to, from, failure) => {
if (failure) {
console.error('Navigation failure:', failure);
// Handle different error types
if (failure.type === 2) { // Redirect abort
// Handle redirect cancellation
} else if (failure.type === 4) { // Navigation error
// Handle navigation error
}
}
});
The failure parameter is available in Vue Router 3.1.0+ and provides details about navigation failures.
Additional Components
router-link
Key properties of the router-link component:
- Uses History API mode - base path handling is automatic
- Intercepts click events to prevent full page reloads
- Renders as an a tag by default; use tag prop for other elements
- Supports name binding and query parameters
<router-link
:to="{ name: 'UserList', query: { status: 'active' } }"
tag="li"
>
Active Users
</router-link>
router-view
The router-view component renders matched components based on the route:
- Supports nested routes through children configuration
- Works with named views using the name prop
- Combines with transition and keep-alive for animation and caching
<transition name="slide-fade" mode="out-in">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
The keep-alive component caches mounted components, preserving they state when navigating between routes.