All skills
onmax avatar

/vue-router-best-practices

@de09c7b official
by Maxonmax/nuxt-skills715 stars
38

Vue Router 4 patterns, navigation guards, route params, and route-component lifecycle interactions.

Use this Skill: https://skilld.dev/gh/onmax/nuxt-skills/vue-router-best-practices

This session only. Nothing lands on disk.

referencerouter-navigation-guard-infinite-loop.md

≈1.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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

  1. Always exclude the target route - Never redirect to a route that would trigger the same redirect
  2. Use route meta fields - Cleaner than path string comparisons
  3. Test edge cases - Direct URL access, refresh, back button
  4. Add logging during development - Helps trace redirect chains
  5. Have an escape hatch - Error page or max redirect count

Reference

Source: SKILL.md on GitHub

No alerts9d3 checks · Risk SAFE
  • Gen Agent Trust Hub9d

    The skill provides comprehensive documentation and best practices for using Vue Router 4, including navigation guards, lifecycle interactions, and proper async handling. No security risks or malicious patterns were identified.

  • Socket9d

    No alerts

  • Snyk9d

    Risk: LOW · No issues

Signed by skilld at de09c7b. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated last week

README badge

README badge for onmax/nuxt-skills/vue-router-best-practices