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:

javascript
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 occurs

The browser eventually stops with "Too many redirects" error.

Symptoms

  • Browser shows "ERR_TOO_MANY_REDIRECTS" or "Redirect loop detected"
  • URL bar flashes between /login and 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: true meta 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. 1.Exclude the login route from the authentication guard:
  2. 2.```javascript
  3. 3.router.beforeEach((to, from, next) => {
  4. 4.const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
  5. 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. 1.Define route meta fields correctly:
  2. 2.```javascript
  3. 3.const routes = [
  4. 4.{
  5. 5.path: '/login',
  6. 6.component: LoginPage,
  7. 7.meta: { public: true }, // Not requiresAuth
  8. 8.},
  9. 9.{
  10. 10.path: '/dashboard',
  11. 11.component: DashboardPage,
  12. 12.meta: { requiresAuth: true },
  13. 13.},
  14. 14.{
  15. 15.path: '/about',
  16. 16.component: AboutPage,
  17. 17.// No meta - accessible to all
  18. 18.},
  19. 19.];
  20. 20.`
  21. 21.Handle async authentication properly:
  22. 22.```javascript
  23. 23.router.beforeEach(async (to, from, next) => {
  24. 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. 1.Prevent navigation to login when already authenticated:
  2. 2.```javascript
  3. 3.router.beforeEach((to, from, next) => {
  4. 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 requiresAuth and public meta 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 isNavigationFailure to detect and handle redirect failures
  • Log navigation events during development to spot unexpected redirect patterns