Setting the file. One moment.
Skill 15 · Vue Router Best Practices
Subchapter 15.5
reference/router-navigation-guard-next-deprecated.mdMarkdown4 KBView on GitHub
Impact: HIGH - The third next() argument in navigation guards is deprecated in Vue Router 4. While still supported for backward compatibility, using it incorrectly is one of the most common sources of bugs: calling it multiple times, forgetting to call it, or calling it conditionally without proper logic.
// WRONG: Using deprecated next() function
router.beforeEach((to, from, next) => {
if (!isAuthenticated) {
next('/login') // Easy to forget this call
}
// BUG: next() not called when authenticated - navigation hangs!
})
// WRONG: Multiple next() calls
router.beforeEach((to, from, next) => {
if (!isAuthenticated) {
next('/login')
}
next() // BUG: Called twice when not authenticated!
})
// WRONG: next() in async code without proper handling
router.beforeEach(async (to, from, next) => {
const user = await fetchUser()
if (!user) {
next('/login')
}
next() // Still gets called even after redirect!
})// CORRECT: Return-based syntax (modern Vue Router 4+)
router.beforeEach((to, from) => {
if (!isAuthenticated) {
return '/login' // Redirect
}
// Return nothing (undefined) to proceed
})
// CORRECT: Return false to cancel navigation
router.beforeEach((to, from) => {
if (hasUnsavedChanges) {
return false // Cancel navigation
}
})
// CORRECT: Async with return-based syntax
router.beforeEach(async (to, from) => {
const user = await fetchUser()
if (!user) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
// Proceed with navigation
})router.beforeEach((to, from) => {
// Return nothing/undefined - allow navigation
return
// Return false - cancel navigation, stay on current route
return false
// Return string path - redirect to path
return '/login'
// Return route object - redirect with full control
return { name: 'Login', query: { redirect: to.fullPath } }
// Return Error - cancel and trigger router.onError()
return new Error('Navigation cancelled')
})If maintaining legacy code that uses next(), follow these rules strictly:
// CORRECT: Exactly one next() call per code path
router.beforeEach((to, from, next) => {
if (!isAuthenticated) {
next('/login')
return // CRITICAL: Exit after calling next()
}
if (!hasPermission(to)) {
next('/forbidden')
return // CRITICAL: Exit after calling next()
}
next() // Only reached if all checks pass
})router.beforeEach(async (to, from) => {
try {
await validateAccess(to)
// Proceed
} catch (error) {
if (error.status === 401) {
return '/login'
}
if (error.status === 403) {
return '/forbidden'
}
// Log error and proceed anyway (or return false)
console.error('Access validation failed:', error)
return false
}
})