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 - 路由参数变化处理:使用
onBeforeRouteUpdate或watch
七、高级用法 #
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 路由守卫:
- 全局前置守卫使用全局中间件实现
- 路由独享守卫使用页面中间件实现
- 组件内守卫使用
onBeforeRouteLeave和onBeforeRouteUpdate - 实现登录重定向、表单确认、权限控制等功能
路由守卫是构建安全、用户友好应用的重要工具,合理使用可以提升用户体验和应用安全性。
最后更新:2026-03-28