Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue Router 4 reference covering navigation guards, route params, lifecycle interactions, and common gotchas.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/router-navigation-guard-infinite-loop.md
1---2title: Navigation Guard Infinite Redirect Loops3impact: HIGH4impactDescription: Misconfigured navigation guards can trap users in infinite redirect loops, crashing the browser or making the app unusable5type: gotcha6tags: [vue3, vue-router, navigation-guards, redirect, debugging]7---89# Navigation Guard Infinite Redirect Loops1011**Impact: HIGH** - A common mistake in navigation guards is creating conditions that cause infinite redirects. Vue Router will detect this and show a warning, but in production, it can crash the browser or create a broken user experience.1213## Task Checklist1415- [ ] Always check if already on target route before redirecting16- [ ] Test guard logic with all possible navigation scenarios17- [ ] Add route meta to control which routes need protection18- [ ] Use Vue Router devtools to debug redirect chains1920## The Problem2122```javascript23// WRONG: Infinite loop - always redirects to login, even when on login!24router.beforeEach((to, from) => {25if (!isAuthenticated()) {26return '/login' // Redirects to /login, which triggers guard again...27}28})2930// WRONG: Circular redirect between two routes31router.beforeEach((to, from) => {32if (to.path === '/dashboard' && !hasProfile()) {33return '/profile'34}35if (to.path === '/profile' && !isVerified()) {36return '/dashboard' // Back to dashboard, which goes to profile...37}38})39```4041**Error you'll see:**42```43[Vue Router warn]: Detected an infinite redirection in a navigation guard when going from "/" to "/login". Aborting to avoid a Stack Overflow.44```4546## Solution 1: Exclude Target Route4748```javascript49// CORRECT: Don't redirect if already going to login50router.beforeEach((to, from) => {51if (!isAuthenticated() && to.path !== '/login') {52return '/login'53}54})5556// CORRECT: Use route name for cleaner check57router.beforeEach((to, from) => {58const publicPages = ['Login', 'Register', 'ForgotPassword']5960if (!isAuthenticated() && !publicPages.includes(to.name)) {61return { name: 'Login' }62}63})64```6566## Solution 2: Use Route Meta Fields6768```javascript69// router.js70const routes = [71{72path: '/login',73name: 'Login',74component: Login,75meta: { requiresAuth: false }76},77{78path: '/dashboard',79name: 'Dashboard',80component: Dashboard,81meta: { requiresAuth: true }82},83{84path: '/public',85name: 'PublicPage',86component: PublicPage,87meta: { requiresAuth: false }88}89]9091// Guard checks meta field92router.beforeEach((to, from) => {93// Only redirect if route requires auth94if (to.meta.requiresAuth && !isAuthenticated()) {95return { name: 'Login', query: { redirect: to.fullPath } }96}97})98```99100## Solution 3: Handle Redirect Chains Carefully101102```javascript103// CORRECT: Break potential circular redirects104router.beforeEach((to, from) => {105// Prevent redirect loops by tracking redirect depth106const redirectCount = to.query._redirectCount || 0107108if (redirectCount > 3) {109console.error('Too many redirects, stopping at:', to.path)110return '/error' // Escape hatch111}112113if (needsRedirect(to)) {114return {115path: getRedirectTarget(to),116query: { ...to.query, _redirectCount: redirectCount + 1 }117}118}119})120```121122## Solution 4: Centralized Redirect Logic123124```javascript125// guards/auth.js126export function createAuthGuard(router) {127const publicRoutes = new Set(['Login', 'Register', 'ForgotPassword', 'ResetPassword'])128const guestOnlyRoutes = new Set(['Login', 'Register'])129130router.beforeEach((to, from) => {131const isPublic = publicRoutes.has(to.name)132const isGuestOnly = guestOnlyRoutes.has(to.name)133const isLoggedIn = isAuthenticated()134135// Not logged in, trying to access protected route136if (!isLoggedIn && !isPublic) {137return { name: 'Login', query: { redirect: to.fullPath } }138}139140// Logged in, trying to access guest-only route (like login page)141if (isLoggedIn && isGuestOnly) {142return { name: 'Dashboard' }143}144145// All other cases: proceed146})147}148```149150## Debugging Redirect Loops151152```javascript153// Add logging to understand the redirect chain154router.beforeEach((to, from) => {155console.log(`Navigation: ${from.path} -> ${to.path}`)156console.log('Auth state:', isAuthenticated())157console.log('Route meta:', to.meta)158159// Your guard logic here160})161162// Or use afterEach for confirmed navigations163router.afterEach((to, from) => {164console.log(`Navigated: ${from.path} -> ${to.path}`)165})166```167168## Common Redirect Loop Patterns169170| Pattern | Problem | Fix |171|---------|---------|-----|172| Auth check without exclusion | Login redirects to login | Exclude `/login` from check |173| Role-based with circular deps | Admin -> User -> Admin | Use single source of truth for role requirements |174| Onboarding flow | Step 1 -> Step 2 -> Step 1 | Track completion state properly |175| Redirect query handling | Reading redirect creates new redirect | Process redirect only once |176177## Key Points1781791. **Always exclude the target route** - Never redirect to a route that would trigger the same redirect1802. **Use route meta fields** - Cleaner than path string comparisons1813. **Test edge cases** - Direct URL access, refresh, back button1824. **Add logging during development** - Helps trace redirect chains1835. **Have an escape hatch** - Error page or max redirect count184185## Reference186- [Vue Router Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html)187- [Vue Router Route Meta Fields](https://router.vuejs.org/guide/advanced/meta.html)188