Skip to content

路由中间件

路由中间件在导航到特定路由前运行,用于权限检查、数据预加载等。

为什么需要路由中间件?

没有中间件有中间件
每个页面单独写登录检查中间件写一次,多个页面复用
容易遗漏某个页面只需在 definePageMeta 中注册
权限逻辑散落各处集中管理,统一维护

路由中间件的本质

一个在路由跳转前自动执行的函数。你可以决定:放行、重定向、还是阻止导航。

中间件类型

类型命名规则运行范围典型用途
全局中间件.global.ts 结尾每次路由变化都执行日志、全局认证
页面中间件普通文件名仅注册了该中间件的页面特定页面的权限检查
布局中间件在布局中注册使用该布局的所有页面管理后台的全局认证

创建中间件

全局中间件

文件名以 .global. 结尾:

ts
// app/middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // 每次路由变化都执行
  console.log(`导航:${from.path} → ${to.path}`)
})

INFO

全局中间件要谨慎使用:它会在每次路由变化时执行 包括首次加载和客户端导航。如果逻辑太重,会拖慢所有页面的加载速度

适合全局中间件的场景

  • 路由日志/分析
  • 全局认证(大多数页面都需要登录)
  • A/B 测试分流

页面级中间件

ts
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const isAuthenticated = useState('isAuthenticated')

  // 未登录则跳转到登录页
  if (!isAuthenticated.value && to.path !== '/login') {
    return navigateTo('/login')
  }
})

为什么要排除 /login

否则会导致死循环:未登录 → 跳转 /login → 中间件再次检查 → 还是未登录 → 再次跳转 → ...

更好的做法

检查目标路由是否需要认证,而不是检查当前状态:

ts
// 只对需要认证的页面生效(通过 definePageMeta 的 meta 标记)
if (to.meta.requiresAuth && !isAuthenticated.value) {
return navigateTo('/login')
}

注册中间件

在页面中注册

vue
<script setup>
definePageMeta({
  // 单个中间件
  middleware: 'auth',

  // 多个中间件(按顺序执行)
  middleware: ['auth', 'admin'],
})
</script>

中间件名称规则

名称基于文件名,去掉 .ts 后缀。如 auth.ts'auth'admin-check.ts'admin-check'

内联中间件

vue
<script setup>
definePageMeta({
  middleware: [
    'auth',  // 引用 app/middleware/auth.ts
    (to, from) => {
      // 内联中间件逻辑(不用单独创建文件)
      if (to.path.includes('/admin')) {
        return navigateTo('/forbidden')
      }
    },
  ],
})
</script>

什么时候用内联中间件?

逻辑非常简单且只在一个页面使用时。如果逻辑复杂或在多个页面使用,还是创建单独的文件更好。

在布局中注册

vue
<!-- app/layouts/admin.vue -->
<script setup>
definePageMeta({
  middleware: 'admin',
})
</script>

<template>
  <div>
    <AdminSidebar />
    <NuxtPage />
  </div>
</template>

布局中间件的效果

所有使用 admin 布局的页面都会自动经过 admin 中间件。不需要在每个页面单独注册。

中间件执行顺序

text
1. 全局中间件(按文件名排序)

2. 布局中间件(definePageMeta 中定义)

3. 页面中间件(definePageMeta 中定义)

4. 路由导航完成

全局中间件的排序

如果有多个全局中间件,按文件名字母顺序执行。可以用数字前缀控制顺序:

text
middleware/
├── 01.log.global.ts      # 第一个执行
├── 02.auth.global.ts     # 第二个执行
└── analytics.global.ts   # 按字母顺序

中间件返回值

ts
export default defineNuxtRouteMiddleware((to, from) => {
  // 1. 无返回值 / 返回 undefined → 允许导航(继续)
  return

  // 2. 返回 navigateTo() → 重定向到其他页面
  return navigateTo('/login')

  // 3. 返回 abortNavigation() → 阻止导航(留在当前页面)
  return abortNavigation()

  // 4. 带错误信息阻止
  return abortNavigation(
    createError({ statusCode: 403, statusMessage: 'Forbidden' })
  )
})

navigateTo vs abortNavigation 的区别

  • navigateTo:"别去那了,去这里"(重定向)
  • abortNavigation:"别去了,留在这"(阻止导航)

INFO

必须有 return

ts
// ❌ 错误:没有 return,导航不会被阻止
navigateTo('/login')

// ✅ 正确
return navigateTo('/login')

常见中间件示例

认证中间件

ts
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
  const token = useCookie('auth-token')

  if (!token.value && to.path !== '/login') {
    return navigateTo('/login', {
      // 登录后返回原页面
      query: { redirect: to.fullPath },
    })
  }
})

query: { redirect: to.fullPath } 的作用

用户访问 /dashboard 被拦到 /login?redirect=/dashboard,登录成功后可以跳回 /dashboard

登录页面的逻辑:

ts
const route = useRoute()
async function onLoginSuccess() {
await navigateTo(route.query.redirect as string || '/')
}

管理员中间件

ts
// app/middleware/admin.ts
export default defineNuxtRouteMiddleware(() => {
  const user = useState('currentUser')

  if (user.value?.role !== 'admin') {
    return abortNavigation(
      createError({ statusCode: 403, statusMessage: 'Forbidden', message: '需要管理员权限' })
    )
  }
})

日志中间件

ts
// app/middleware/log.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`[Navigation] ${from.path} → ${to.path}`)
})

会员中间件

ts
// app/middleware/vip.ts
export default defineNuxtRouteMiddleware(() => {
  const user = useState('currentUser')

  if (!user.value?.isVip) {
    return navigateTo('/subscribe')
  }
})

注意事项

1. 中间件文件自动注册

app/middleware/ 下的文件自动注册,无需手动 import。

2. SSR 兼容

中间件在 SSR 和客户端都会执行。所以:

  • 不要在中间件中使用浏览器 API(windowdocument
  • 使用 SSR 安全的方法(useCookieuseStatenavigateTo

3. 避免副作用

ts
// ❌ 不好的做法:中间件修改状态
export default defineNuxtRouteMiddleware(() => {
  const count = useState('count')
  count.value++  // 有副作用!每次导航都会改变状态
})

// ✅ 好的做法:中间件只做检查和重定向
export default defineNuxtRouteMiddleware(() => {
  const isAuthenticated = useCookie('auth-token')
  if (!isAuthenticated.value) {
    return navigateTo('/login')
  }
})

4. 命名冲突

中间件名称基于文件名,避免重复命名。如果两个文件叫 auth.ts(在不同子目录下),可能冲突。

5. 中间件中的异步操作

ts
// 中间件中可以使用 async/await
export default defineNuxtRouteMiddleware(async (to) => {
  const user = await $fetch('/api/me')
  if (!user) {
    return navigateTo('/login')
  }
})

INFO

注意:中间件中的异步操作会阻塞导航 如果操作很慢,用户会感觉页面卡住。尽量让中间件逻辑轻量

知识脉络

text
路由导航 → 你在这里:路由中间件

              ├─→ 下一步:路由守卫(另一种守卫方式)

              └─→ 相关:页面元信息(meta 传递数据给中间件)

基于 Nuxt 4 官方文档整理编写