Vue Router Multiguard
This package provides a utility to combine multiple Vue Router navigation guards into a single `beforeEnter` or `beforeEach` hook. It allows developers to specify an array of functions that will be executed serially, stopping when any guard calls `next()` with an argument (e.g., a path to redirect to) or when all guards complete without redirection. The current stable version is 1.0.3, which includes official TypeScript support, making it well-integrated into modern Vue 2/3 applications using TypeScript. As a lightweight utility, its release cadence is generally driven by bug fixes or minor enhancements. Its primary differentiator is simplifying the composition of multiple guards without complex nesting or manual chaining, providing a clean API for common use cases like layered authentication and authorization.
Common errors
-
TypeError: multiguard is not a function
cause This error typically occurs when `multiguard` is imported incorrectly, often by using a named import syntax (`import { multiguard } from '...'`) for a module that provides a default export.fixChange your import statement to `import multiguard from 'vue-router-multiguard';`. If using CommonJS `require`, ensure you access the default export correctly: `const multiguard = require('vue-router-multiguard').default;` -
NavigationDuplicated: Avoided redundant navigation to current location
cause This warning (or similar) from Vue Router can appear if a guard redirects to the current route or attempts to push the same route multiple times consecutively. While not directly caused by `multiguard`, it's a common outcome of guard logic.fixEnsure your guards' redirection logic only fires when necessary. Before calling `next(path)`, you might add a check like `if (to.path !== path) { next(path); } else { next(false); }` to prevent unnecessary navigation calls if already on the target path.
Warnings
- gotcha Guards passed to `multiguard` are executed strictly in the order they are supplied. If an early guard calls `next()` with an argument (e.g., a path to redirect to), subsequent guards in the `multiguard` chain will be bypassed, and the route will not be entered. Ensure critical checks are placed earlier in the array.
- gotcha Calling `next(false)` inside any guard within `multiguard` will immediately cancel the current navigation without redirecting or resolving any route. This can lead to a 'stuck' state if not handled gracefully in the UI.
- gotcha TypeScript types for `vue-router-multiguard` were officially added in version 1.0.3. If you are working in a TypeScript project and using an older version (e.g., v1.0.2 or earlier), you will not have native type definitions available.
Install
-
npm install vue-router-multiguard -
yarn add vue-router-multiguard -
pnpm add vue-router-multiguard
Imports
- multiguard
import { multiguard } from 'vue-router-multiguard';import multiguard from 'vue-router-multiguard';
- multiguard (CommonJS)
const multiguard = require('vue-router-multiguard');const multiguard = require('vue-router-multiguard').default; - Type definitions
import { Multiguard } from 'vue-router-multiguard/dist/types';import multiguard, { Multiguard } from 'vue-router-multiguard';
Quickstart
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import multiguard from 'vue-router-multiguard';
Vue.use(VueRouter);
// Example guard functions
const requireAuth = (to, from, next) => {
const isAuthenticated = localStorage.getItem('userToken'); // Simulate auth check
if (to.meta?.requiresAuth && !isAuthenticated) {
console.log(`Guard 'requireAuth' blocked for ${to.path}: User not authenticated.`);
next('/login'); // Redirect to login page
} else {
next(); // Proceed
}
};
const requireAdmin = (to, from, next) => {
const userRole = localStorage.getItem('userRole'); // Simulate role check
if (to.meta?.requiresAdmin && userRole !== 'admin') {
console.log(`Guard 'requireAdmin' blocked for ${to.path}: Not an administrator.`);
next('/access-denied'); // Redirect for insufficient permissions
} else {
next(); // Proceed
}
};
const logRoute = (to, from, next) => {
console.log(`Guard 'logRoute' entered: ${from.path} -> ${to.path}`);
next(); // Always proceed
};
const routes: Array<RouteConfig> = [
{
path: '/',
component: { template: '<div><h1>Home</h1><p>Public access.</p></div>' },
name: 'home'
},
{
path: '/dashboard',
component: { template: '<div><h1>Dashboard</h1><p>Authenticated access.</p></div>' },
name: 'dashboard',
meta: { requiresAuth: true },
beforeEnter: multiguard([logRoute, requireAuth]) // Combined guards
},
{
path: '/admin',
component: { template: '<div><h1>Admin Panel</h1><p>Admin-only access.</p></div>' },
name: 'admin',
meta: { requiresAuth: true, requiresAdmin: true },
beforeEnter: multiguard([logRoute, requireAuth, requireAdmin]) // Multiple combined guards
},
{
path: '/login',
component: { template: '<div><h1>Login</h1><p>Please log in.</p></div>' },
name: 'login'
},
{
path: '/access-denied',
component: { template: '<div><h1>Access Denied</h1><p>You do not have permission to view this page.</p></div>' },
name: 'access-denied'
}
];
const router = new VueRouter({ routes });
// Simulate user state for demonstration
localStorage.setItem('userToken', 'fake-jwt-token-123'); // User is 'logged in'
localStorage.setItem('userRole', 'user'); // User is not 'admin'
console.log('--- Navigating to /dashboard (authenticated user) ---');
router.push('/dashboard').catch(err => console.error('Navigation error:', err.message));
setTimeout(() => {
console.log('\n--- Navigating to /admin (authenticated but not admin) ---');
router.push('/admin').catch(err => console.error('Navigation error:', err.message));
}, 100);
setTimeout(() => {
console.log('\n--- Navigating to /dashboard (unauthenticated user) ---');
localStorage.removeItem('userToken'); // Simulate logout
router.push('/dashboard').catch(err => console.error('Navigation error:', err.message));
}, 200);
// For a real app, you'd mount this to a Vue instance:
// new Vue({ router, el: '#app' });