Introduction
Vue Router's beforeEach navigation guard is commonly used for authentication checks, redirecting unauthenticated users to the login page. When the guard logic is incomplete, it creates an infinite redirect loop between the protected page and the login page:
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login'); // Redirects to /login
} else {
next();
}
});
// If /login also triggers the guard incorrectly, infinite loop occursThe browser eventually stops with "Too many redirects" error.
Symptoms
- Browser shows "ERR_TOO_MANY_REDIRECTS" or "Redirect loop detected"
- URL bar flashes between
/loginand the protected route - Vue Router logs repeated navigation events
- Authenticated users get redirected to login and back repeatedly
- The loop occurs only for specific routes or after token expiration
Common Causes
- Login route also has
requiresAuth: truemeta field - Authentication check returns false during async token validation
next()called without arguments in a guard that should redirect- Token stored in localStorage but check runs before localStorage is available (SSR)
- Redirect target (
/login) is also protected by the same guard
Step-by-Step Fix
- 1.Exclude the login route from the authentication guard:
- 2.```javascript
- 3.router.beforeEach((to, from, next) => {
- 4.const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
- 5.const isPublicRoute = to.matched.some(record => record.meta.public);
if (requiresAuth && !isAuthenticated()) { next({ path: '/login', query: { redirect: to.fullPath } }); } else if (isPublicRoute && isAuthenticated()) { next({ path: '/' }); // Authenticated users should not see login } else { next(); } }); ```
- 1.Define route meta fields correctly:
- 2.```javascript
- 3.const routes = [
- 4.{
- 5.path: '/login',
- 6.component: LoginPage,
- 7.meta: { public: true }, // Not requiresAuth
- 8.},
- 9.{
- 10.path: '/dashboard',
- 11.component: DashboardPage,
- 12.meta: { requiresAuth: true },
- 13.},
- 14.{
- 15.path: '/about',
- 16.component: AboutPage,
- 17.// No meta - accessible to all
- 18.},
- 19.];
- 20.
` - 21.Handle async authentication properly:
- 22.```javascript
- 23.router.beforeEach(async (to, from, next) => {
- 24.const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth) { try { const authenticated = await checkAuth(); if (authenticated) { next(); } else { next({ path: '/login', query: { redirect: to.fullPath } }); } } catch (error) { next({ path: '/login', query: { redirect: to.fullPath } }); } } else { next(); } }); ```
- 1.Prevent navigation to login when already authenticated:
- 2.```javascript
- 3.router.beforeEach((to, from, next) => {
- 4.const auth = isAuthenticated();
if (to.path === '/login' && auth) { next(from.query.redirect || '/'); return; }
if (to.matched.some(r => r.meta.requiresAuth) && !auth) { next({ path: '/login', query: { redirect: to.fullPath } }); return; }
next(); }); ```
Prevention
- Always define both
requiresAuthandpublicmeta fields explicitly for every route - Never call
next('/login')without checking if the current route is already/login - Use named routes instead of path strings for redirects:
next({ name: 'login' }) - Add a maximum redirect counter as a safety net:
- ```javascript
- let redirectCount = 0;
- router.beforeEach((to, from, next) => {
- redirectCount++;
- if (redirectCount > 10) {
- redirectCount = 0;
- next(false); // Stop navigation
- console.error('Navigation loop detected');
- return;
- }
- // ... rest of guard
- });
`- Test authentication flows with expired tokens, invalid tokens, and no token
- Use Vue Router's
isNavigationFailureto detect and handle redirect failures - Log navigation events during development to spot unexpected redirect patterns