在使用Nuxt.js开发应用时,路由守卫(Route Guards)是保护页面访问权限的重要工具。然而,很多开发者在实现路由守卫时会遇到一个常见但容易被忽视的问题:当用户访问一个不存在的路由时,也会被强制重定向到登录页面。这不仅影响了用户体验,还可能导致一些意外的行为。本文将详细介绍这个问题的成因和解决方案。
假设我们有一个Nuxt应用,配置了如下的路由守卫中间件:·
// middleware/auth.client.ts
export default defineNuxtRouteMiddleware(async (to) => {
const auth = useAuthStore()
const loggedIn = auth.loggedIn || !!useCookie<string | null>('access_token').value
const publicPaths = ['/login', '/register', '/forgot-password', '/']
const isPublic = publicPaths.includes(to.path)
// 未登录用户访问受保护页面时跳转到登录
if (!isPublic && !loggedIn) {
return navigateTo('/login')
}
})
这个守卫的逻辑看起来很合理:如果用户访问的不是公共路径且未登录,就重定向到登录页。
但是,当用户访问一个不存在的页面(比如 /test/ 或 /unknown-page)时,也会被重定向到登录页,而不是显示404页面。
问题根源
问题的根本原因在于:路由守卫无法区分”受保护的路由”和”不存在的路由”。
在我们的中间件逻辑中:
publicPaths 包含了已知的公共路径
所有不在 publicPaths 中的路径都被视为”需要登录的受保护路径”
但实际上,还有第三种情况:应用中根本不存在的路径。对于这些路径,我们不应该进行任何登录检查,而应该让Nuxt显示404页面。
解决方案
方法一:检查路由匹配状态(推荐)
export default defineNuxtRouteMiddleware(async (to) => {
const auth = useAuthStore()
const loggedIn = auth.loggedIn || !!useCookie<string | null>('access_token').value
const publicPaths = ['/login', '/register', '/forgot-password', '/']
const isPublic = publicPaths.includes(to.path)
// 关键修复:检查当前路由是否匹配到了有效路由
if (!to.matched || to.matched.length === 0) {
// 如果是未知路由,直接返回,让Nuxt显示404页面
return
}
if (!isPublic && !loggedIn) {
return navigateTo('/login')
}
})
核心原理:
to.matched 是Vue Router提供的属性,包含与当前路径匹配的所有路由记录
如果路径在Nuxt的路由系统中没有定义(pages目录下没有对应文件),to.matched会是空数组
通过这个判断,我们可以识别并放行未知路由
方法二:显式定义所有有效路由
如果你希望更精确地控制,可以显式列出所有有效路由:
const validRoutes = [
'/', '/login', '/register', '/forgot-password',
'/dashboard', '/profile', '/settings'
// ... 所有你应用中实际存在的路由
]
export default defineNuxtRouteMiddleware(async (to) => {
// ... 其他代码
// 如果访问的路由不在有效路由列表中,直接返回
if (!validRoutes.includes(to.path)) {
return
}
// ... 权限检查逻辑
})
完整的最佳实践示例
// middleware/auth.client.ts
import { useAuthStore } from '../store/auth'
export default defineNuxtRouteMiddleware(async (to) => {
const auth = useAuthStore()
const loggedIn = auth.loggedIn || !!useCookie<string | null>('access_token').value
const publicPaths = ['/login', '/register', '/forgot-password', '/', '/admin/login']
const adminPaths = ['/admin/dashboard', '/admin/users']
const isPublic = publicPaths.includes(to.path) || to.path.startsWith('/p/')
const isAdminPath = to.path.startsWith('/admin/') && !publicPaths.includes(to.path)
// 第一步:检查是否为未知路由
if (!to.matched || to.matched.length === 0) {
return // 让Nuxt显示404页面
}
// 第二步:登录状态检查
if (!isPublic && !loggedIn) {
if (isAdminPath) {
return navigateTo('/admin/login')
}
return navigateTo('/login')
}
// 第三步:权限验证(仅客户端)
if (isAdminPath && loggedIn && import.meta.client) {
await nextTick()
if (!auth.isAdmin) {
return navigateTo('/admin/login')
}
}
})
在实现Nuxt路由守卫时,记住要考虑三种类型的路由:
公共路由 – 任何人都可以访问
受保护路由 – 需要登录才能访问
未知路由 – 应用中不存在的路径,应该显示404页面
通过检查 to.matched 数组,我们可以准确识别未知路由,避免不必要的登录重定向,提供更好的用户体验。
这个看似简单的问题提醒我们:在编写路由守卫时,要考虑所有可能的路由情况,而不仅仅是”已登录”和”未登录”两种状态。