Nuxt.js路由守卫 #

一、路由守卫概述 #

路由守卫是控制导航流程的机制,可以在导航的不同阶段执行代码。Nuxt.js 的路由守卫主要通过中间件和插件实现。

1.1 守卫类型 #

守卫类型 执行时机 实现方式
全局前置守卫 导航开始前 全局中间件
路由独享守卫 匹配路由时 页面中间件
组件内守卫 组件生命周期 组合式函数

1.2 导航流程 #

text
导航触发
    ↓
全局前置守卫(全局中间件)
    ↓
路由独享守卫(页面中间件)
    ↓
组件内守卫
    ↓
导航确认
    ↓
更新 DOM

二、全局前置守卫 #

2.1 使用全局中间件 #

middleware/router.global.ts

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`导航: ${from?.path} → ${to.path}`)
})

2.2 导航日志 #

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const startTime = Date.now()
  
  return () => {
    const duration = Date.now() - startTime
    console.log(`导航完成,耗时: ${duration}ms`)
  }
})

2.3 全局权限控制 #

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const publicPages = ['/', '/login', '/register', '/about']
  const isPublicPage = publicPages.includes(to.path)
  
  if (!isPublicPage) {
    const token = useCookie('token')
    if (!token.value) {
      return navigateTo('/login')
    }
  }
})

三、路由独享守卫 #

3.1 页面级中间件 #

vue
<script setup lang="ts">
definePageMeta({
  middleware: [
    (to, from) => {
      if (to.params.id === 'forbidden') {
        return navigateTo('/error')
      }
    }
  ]
})
</script>

3.2 命名中间件 #

middleware/guest.ts

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()
  
  if (isAuthenticated.value) {
    return navigateTo('/dashboard')
  }
})

使用:

vue
<script setup lang="ts">
definePageMeta({
  middleware: 'guest'
})
</script>

四、组件内守卫 #

4.1 使用watch监听路由 #

vue
<script setup lang="ts">
const route = useRoute()

watch(
  () => route.params.id,
  (newId, oldId) => {
    console.log(`ID 变化: ${oldId} → ${newId}`)
  }
)
</script>

4.2 使用onBeforeRouteLeave #

vue
<script setup lang="ts">
const hasUnsavedChanges = ref(false)

onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const confirm = window.confirm('有未保存的更改,确定要离开吗?')
    if (!confirm) return false
  }
})
</script>

4.3 使用onBeforeRouteUpdate #

vue
<script setup lang="ts">
onBeforeRouteUpdate((to, from) => {
  if (to.params.id !== from.params.id) {
    fetchData(to.params.id)
  }
})

const fetchData = async (id: string | string[]) => {
  // 获取数据
}
</script>

五、常见守卫场景 #

5.1 登录重定向 #

middleware/auth.ts

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()
  
  if (!isAuthenticated.value) {
    const redirectPath = to.fullPath
    return navigateTo({
      path: '/login',
      query: { redirect: redirectPath }
    })
  }
})

登录后重定向:

vue
<script setup lang="ts">
const route = useRoute()
const { login } = useAuth()

const handleLogin = async () => {
  await login(credentials)
  
  const redirect = route.query.redirect as string
  navigateTo(redirect || '/dashboard')
}
</script>

5.2 表单离开确认 #

vue
<script setup lang="ts">
const form = reactive({
  title: '',
  content: ''
})

const isDirty = computed(() => {
  return form.title !== '' || form.content !== ''
})

onBeforeRouteLeave((to, from) => {
  if (isDirty.value) {
    return confirm('有未保存的更改,确定要离开吗?')
  }
})
</script>

5.3 页面访问统计 #

middleware/analytics.global.ts

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  if (import.meta.client) {
    const { trackPageView } = useAnalytics()
    trackPageView(to.path)
  }
})

5.4 进度条控制 #

middleware/loading.global.ts

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const nuxtApp = useNuxtApp()
  
  nuxtApp.hook('page:loading:start', () => {
    console.log('页面加载开始')
  })
  
  nuxtApp.hook('page:loading:end', () => {
    console.log('页面加载结束')
  })
})

六、守卫与中间件的区别 #

6.1 功能对比 #

功能 中间件 守卫
全局控制 ✅ 全局中间件
页面控制 ✅ 页面中间件 ✅ 组件内守卫
离开确认 ✅ onBeforeRouteLeave
路由更新 ✅ onBeforeRouteUpdate
返回清理函数

6.2 选择建议 #

  • 导航前验证:使用中间件
  • 离开确认:使用 onBeforeRouteLeave
  • 路由参数变化处理:使用 onBeforeRouteUpdatewatch

七、高级用法 #

7.1 守卫链 #

typescript
export default defineNuxtRouteMiddleware(async (to, from) => {
  const guards = [
    authGuard,
    roleGuard,
    permissionGuard
  ]
  
  for (const guard of guards) {
    const result = await guard(to, from)
    if (result) return result
  }
})

async function authGuard(to, from) {
  const { isAuthenticated } = useAuth()
  if (!isAuthenticated.value) {
    return navigateTo('/login')
  }
}

async function roleGuard(to, from) {
  const { user } = useAuth()
  if (to.meta.roles && !to.meta.roles.includes(user.value?.role)) {
    throw createError({ statusCode: 403 })
  }
}

async function permissionGuard(to, from) {
  const { hasPermission } = usePermissions()
  if (!hasPermission(to.meta.permission)) {
    throw createError({ statusCode: 403 })
  }
}

7.2 条件守卫 #

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const config = useRuntimeConfig()
  
  if (config.public.enableAuth) {
    const { isAuthenticated } = useAuth()
    if (!isAuthenticated.value) {
      return navigateTo('/login')
    }
  }
})

7.3 守卫状态管理 #

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const guardState = useState('guard-state', () => ({
    lastNavigation: null as string | null,
    navigationCount: 0
  }))
  
  guardState.value.lastNavigation = to.path
  guardState.value.navigationCount++
  
  console.log(`导航次数: ${guardState.value.navigationCount}`)
})

八、调试守卫 #

8.1 开发模式日志 #

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  if (process.dev) {
    console.group('🛡️ 路由守卫')
    console.log('来源:', from?.path)
    console.log('目标:', to.path)
    console.log('参数:', to.params)
    console.log('查询:', to.query)
    console.log('元信息:', to.meta)
    console.groupEnd()
  }
})

8.2 性能监控 #

typescript
export default defineNuxtRouteMiddleware((to, from) => {
  const start = performance.now()
  
  return () => {
    const duration = performance.now() - start
    if (duration > 100) {
      console.warn(`守卫执行时间过长: ${duration.toFixed(2)}ms`)
    }
  }
})

九、完整示例 #

9.1 完整权限控制系统 #

middleware/auth.global.ts

typescript
interface RouteMeta {
  requiresAuth?: boolean
  roles?: string[]
  permissions?: string[]
}

export default defineNuxtRouteMiddleware(async (to, from) => {
  const meta = to.meta as RouteMeta
  const { isAuthenticated, user, checkPermission } = useAuth()
  
  if (meta.requiresAuth === false) {
    return
  }
  
  if (!isAuthenticated.value) {
    return navigateTo({
      path: '/login',
      query: { redirect: to.fullPath }
    })
  }
  
  if (meta.roles && meta.roles.length > 0) {
    if (!meta.roles.includes(user.value?.role)) {
      throw createError({
        statusCode: 403,
        message: '您没有访问此页面的权限'
      })
    }
  }
  
  if (meta.permissions && meta.permissions.length > 0) {
    const hasAllPermissions = meta.permissions.every(
      permission => checkPermission(permission)
    )
    
    if (!hasAllPermissions) {
      throw createError({
        statusCode: 403,
        message: '您没有所需的权限'
      })
    }
  }
})

pages/admin/users.vue

vue
<script setup lang="ts">
definePageMeta({
  meta: {
    requiresAuth: true,
    roles: ['admin'],
    permissions: ['users:read']
  }
})
</script>

<template>
  <div>
    <h1>用户管理</h1>
  </div>
</template>

9.2 表单编辑守卫 #

pages/post/edit/[id].vue

vue
<script setup lang="ts">
const route = useRoute()
const router = useRouter()

const { data: post } = await useFetch(`/api/posts/${route.params.id}`)

const form = reactive({
  title: post.value?.title || '',
  content: post.value?.content || ''
})

const isDirty = computed(() => {
  return form.title !== post.value?.title || 
         form.content !== post.value?.content
})

onBeforeRouteLeave((to, from) => {
  if (isDirty.value) {
    const confirm = window.confirm('有未保存的更改,确定要离开吗?')
    if (!confirm) return false
  }
})

const save = async () => {
  await $fetch(`/api/posts/${route.params.id}`, {
    method: 'PUT',
    body: form
  })
  
  post.value = { ...post.value, ...form }
  navigateTo('/posts')
}
</script>

<template>
  <div>
    <h1>编辑文章</h1>
    <form @submit.prevent="save">
      <input v-model="form.title" />
      <textarea v-model="form.content" />
      <button type="submit">保存</button>
    </form>
  </div>
</template>

十、总结 #

本章介绍了 Nuxt.js 路由守卫:

  • 全局前置守卫使用全局中间件实现
  • 路由独享守卫使用页面中间件实现
  • 组件内守卫使用 onBeforeRouteLeaveonBeforeRouteUpdate
  • 实现登录重定向、表单确认、权限控制等功能

路由守卫是构建安全、用户友好应用的重要工具,合理使用可以提升用户体验和应用安全性。

最后更新:2026-03-28