963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
5.7 KiB
5.7 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Navigation Guard Infinite Redirect Loops | HIGH | Misconfigured navigation guards can trap users in infinite redirect loops, crashing the browser or making the app unusable | gotcha |
|
Navigation Guard Infinite Redirect Loops
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.
Task Checklist
- Always check if already on target route before redirecting
- Test guard logic with all possible navigation scenarios
- Add route meta to control which routes need protection
- Use Vue Router devtools to debug redirect chains
The Problem
// WRONG: Infinite loop - always redirects to login, even when on login!
router.beforeEach((to, from) => {
if (!isAuthenticated()) {
return '/login'; // Redirects to /login, which triggers guard again...
}
});
// WRONG: Circular redirect between two routes
router.beforeEach((to, from) => {
if (to.path === '/dashboard' && !hasProfile()) {
return '/profile';
}
if (to.path === '/profile' && !isVerified()) {
return '/dashboard'; // Back to dashboard, which goes to profile...
}
});
Error you'll see:
[Vue Router warn]: Detected an infinite redirection in a navigation guard when going from "/" to "/login". Aborting to avoid a Stack Overflow.
Solution 1: Exclude Target Route
// CORRECT: Don't redirect if already going to login
router.beforeEach((to, from) => {
if (!isAuthenticated() && to.path !== '/login') {
return '/login';
}
});
// CORRECT: Use route name for cleaner check
router.beforeEach((to, from) => {
const publicPages = ['Login', 'Register', 'ForgotPassword'];
if (!isAuthenticated() && !publicPages.includes(to.name)) {
return { name: 'Login' };
}
});
Solution 2: Use Route Meta Fields
// router.js
const routes = [
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false },
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true },
},
{
path: '/public',
name: 'PublicPage',
component: PublicPage,
meta: { requiresAuth: false },
},
];
// Guard checks meta field
router.beforeEach((to, from) => {
// Only redirect if route requires auth
if (to.meta.requiresAuth && !isAuthenticated()) {
return { name: 'Login', query: { redirect: to.fullPath } };
}
});
Solution 3: Handle Redirect Chains Carefully
// CORRECT: Break potential circular redirects
router.beforeEach((to, from) => {
// Prevent redirect loops by tracking redirect depth
const redirectCount = to.query._redirectCount || 0;
if (redirectCount > 3) {
console.error('Too many redirects, stopping at:', to.path);
return '/error'; // Escape hatch
}
if (needsRedirect(to)) {
return {
path: getRedirectTarget(to),
query: { ...to.query, _redirectCount: redirectCount + 1 },
};
}
});
Solution 4: Centralized Redirect Logic
// guards/auth.js
export function createAuthGuard(router) {
const publicRoutes = new Set(['Login', 'Register', 'ForgotPassword', 'ResetPassword']);
const guestOnlyRoutes = new Set(['Login', 'Register']);
router.beforeEach((to, from) => {
const isPublic = publicRoutes.has(to.name);
const isGuestOnly = guestOnlyRoutes.has(to.name);
const isLoggedIn = isAuthenticated();
// Not logged in, trying to access protected route
if (!isLoggedIn && !isPublic) {
return { name: 'Login', query: { redirect: to.fullPath } };
}
// Logged in, trying to access guest-only route (like login page)
if (isLoggedIn && isGuestOnly) {
return { name: 'Dashboard' };
}
// All other cases: proceed
});
}
Debugging Redirect Loops
// Add logging to understand the redirect chain
router.beforeEach((to, from) => {
console.log(`Navigation: ${from.path} -> ${to.path}`);
console.log('Auth state:', isAuthenticated());
console.log('Route meta:', to.meta);
// Your guard logic here
});
// Or use afterEach for confirmed navigations
router.afterEach((to, from) => {
console.log(`Navigated: ${from.path} -> ${to.path}`);
});
Common Redirect Loop Patterns
| Pattern | Problem | Fix |
|---|---|---|
| Auth check without exclusion | Login redirects to login | Exclude /login from check |
| Role-based with circular deps | Admin -> User -> Admin | Use single source of truth for role requirements |
| Onboarding flow | Step 1 -> Step 2 -> Step 1 | Track completion state properly |
| Redirect query handling | Reading redirect creates new redirect | Process redirect only once |
Key Points
- Always exclude the target route - Never redirect to a route that would trigger the same redirect
- Use route meta fields - Cleaner than path string comparisons
- Test edge cases - Direct URL access, refresh, back button
- Add logging during development - Helps trace redirect chains
- Have an escape hatch - Error page or max redirect count