Nuxt: 中间件

最后更新:2026-08-26

Bob 发现 Alice 能直接访问 /admin 管理后台,毫无权限检查。Charlie 需要一个路由守卫——未登录用户访问管理页面自动跳转到登录页,普通用户不能访问管理员路由。Nuxt 3 的中间件系统就是解决方案。

1. 你将学到


2. 一个管理员的真实故事

(1) 痛点:无权限控制的致命漏洞

Alice 在浏览器输入 /admin 就进了 MegaShop 管理后台,能看到所有订单和用户数据。Bob 作为管理员,发现自己和普通用户看到的界面完全一样,没有任何区分。Charlie 紧急需要路由级别的权限守卫。

(2) 路由中间件的解法

Nuxt 3 中间件在路由跳转前执行,可以重定向或阻止访问:

TYPESCRIPT
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
  if (!isAuthenticated()) return navigateTo('/login')
})

(3) 收益:路由级权限控制

Alice 再也无法直接访问 /admin,未登录自动跳转登录页。Bob 的管理员权限在路由层面得到保障。


3. 三种中间件模式

(1) 中间件执行顺序

100%
flowchart LR
    A[Route Navigation] --> B[Global Middleware 1]
    B --> C[Global Middleware 2]
    C --> D[Named/Inline Middleware]
    D --> E[Page Render]
    D -->|abort| F[Redirect / Abort]
    B -->|abort| F
    C -->|abort| F

(2) 三种模式对比

维度 全局中间件 命名中间件 内联中间件
文件名 *.global.ts *.ts(无 global) 无文件
生效范围 所有路由 指定页面 当前页面
定义方式 middleware/ 目录 middleware/ 目录 definePageMeta 内
复用性 ✅ 自动复用 ✅ 多页引用 ❌ 仅当前页
适用场景 全局鉴权/日志 特定页面守卫 一次性简单检查

4. 全局中间件

▶ 示例:全局鉴权中间件

TYPESCRIPT
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // Skip auth for public pages
  const publicPages = ['/', '/products', '/about', '/login', '/register']
  if (publicPages.includes(to.path)) return

  const token = useCookie('auth-token')
  if (!token.value) {
    return navigateTo(`/login?redirect=${to.path}`, { redirectCode: 302 })
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:全局日志中间件

TYPESCRIPT
// middleware/log.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`Navigation: ${from.path} → ${to.path}`)
  // No return = allow navigation
})

输出:

TEXT 📖 仅展示
// 执行成功
⚠️ 注意: 全局中间件按文件名字母顺序执行。01-auth.global.ts 先于 02-log.global.ts。建议用数字前缀控制顺序。


5. 命名中间件

▶ 示例:管理员角色中间件

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

  if (!user.value) {
    return navigateTo(`/login?redirect=${to.path}`)
  }

  if (user.value.role !== 'admin') {
    // Regular user trying to access admin page
    throw createError({
      statusCode: 403,
      message: 'Access denied. Admin role required.'
    })
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:页面引用命名中间件

VUE
<!-- pages/admin/index.vue -->
<template>
  <div>
    <h1>Admin Dashboard</h1>
    <p>Welcome, Bob (Admin)</p>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: 'admin', // Reference named middleware
  layout: 'sidebar'
})
</script>

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:组合多个中间件

VUE
<!-- pages/admin/settings.vue -->
<template>
  <div>
    <h1>Admin Settings</h1>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: ['auth', 'admin'], // Execute in order: auth → admin
  layout: 'sidebar'
})
</script>

输出:

TEXT 📖 仅展示
// 执行成功

6. 内联中间件

▶ 示例:页面内定义中间件

VUE
<!-- pages/checkout.vue -->
<template>
  <div>
    <h1>Checkout</h1>
    <p>Cart total: ${{ total }} USD</p>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: defineNuxtRouteMiddleware((to) => {
    // Inline: check if cart has items
    const cart = useState<any[]>('cart')
    if (cart.value.length === 0) {
      return navigateTo('/cart')
    }
  })
})

const cart = useState<any[]>('cart')
const total = computed(() => cart.value.reduce((s, i) => s + i.price, 0))
</script>

输出:

TEXT 📖 仅展示
// 执行成功

(1) 内联 vs 命名中间件选择

场景 选择 理由
全站鉴权 全局 每个路由都要检查
管理员路由 命名 多个 admin 页面共享
购物车非空检查 内联 只在 checkout 页用
VIP 专区 命名 可能在多个页面使用
A/B 测试分流 内联 一次性逻辑

7. 中间件与 SSR

(1) SSR 双重执行机制

100%
flowchart TB
    A[User navigates to /admin] --> B[Server: middleware executes]
    B -->|redirect| C[Server sends redirect response]
    B -->|allow| D[Server renders page]
    D --> E[Client: middleware executes again]
    E -->|redirect| F[Client-side redirect]
    E -->|allow| G[Client hydrates page]

▶ 示例:纯客户端中间件

TYPESCRIPT
// middleware/client-only.ts
export default defineNuxtRouteMiddleware((to) => {
  // Skip on server - only check on client
  if (import.meta.server) return

  const localStorage = window.localStorage
  const preferences = JSON.parse(localStorage.getItem('user-preferences') || '{}')

  if (preferences.theme === 'dark') {
    // Client-only logic using browser APIs
    document.documentElement.classList.add('dark')
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

(2) SSR 中间件注意事项

问题 原因 解决方案
中间件执行两次 SSR + 客户端各一次 正常行为,确保幂等
使用 window/localStorage SSR 无浏览器环境 if (import.meta.client) 判断
Cookie 在 SSR 可用 请求携带 Cookie 用 useCookie 读取
navigateTo 在 SSR 返回 302 重定向 正常行为
useState 在中间件 ✅ 可用 适合检查登录状态

8. 综合示例:MegaShop 权限守卫体系

TYPESCRIPT
// middleware/01-auth.global.ts - Global auth check
export default defineNuxtRouteMiddleware((to) => {
  const publicPaths = ['/', '/products', '/products/**', '/about', '/login', '/register']
  const isPublic = publicPaths.some(path => {
    if (path.includes('**')) return to.path.startsWith(path.replace('/**', ''))
    return path === to.path
  })

  if (isPublic) return

  const token = useCookie('auth-token')
  if (!token.value) {
    return navigateTo(`/login?redirect=${encodeURIComponent(to.path)}`)
  }
})
TYPESCRIPT
// middleware/admin.ts - Admin role check
export default defineNuxtRouteMiddleware((to) => {
  const user = useCookie('user-data')

  if (!user.value) {
    return navigateTo('/login')
  }

  if ((user.value as any).role !== 'admin') {
    throw createError({ statusCode: 403, message: 'Admin access required' })
  }
})
VUE
<!-- pages/admin/products/index.vue -->
<template>
  <div>
    <h1>Product Management</h1>
    <p>Manage 1 million products</p>
    <!-- Admin product CRUD interface -->
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: ['admin'],
  layout: 'sidebar'
})
</script>

❓ 常见问题

Q 中间件执行顺序怎么控制?
A 全局中间件按文件名字母排序,建议用数字前缀(01-xxx.global.ts)。命名中间件按 definePageMeta 中的数组顺序执行。
Q 中间件里能做异步操作吗?
A defineNuxtRouteMiddleware 本身不支持 async。但可以在中间件内调用同步 API(如 useCookie/useState)。需要异步验证时,建议在插件或页面 setup 中处理。
Q 为什么中间件在 SSR 和客户端各执行一次?
A SSR 时中间件检查路由权限决定是否渲染,客户端水合时再次执行确保一致性。两次执行的逻辑应该幂等。
Q 全局中间件对性能有影响吗?
A 每次路由跳转都会执行所有全局中间件。保持中间件逻辑轻量,避免在全局中间件中做重计算或 API 调用。
Q 中间件里抛出 createError 会怎样?
A SSR 时返回对应状态码的 HTML 错误页(如 403 页面)。客户端时显示 Nuxt 错误页面。可以自定义 error.vue。
Q 如何在中间件间传递数据?
A 用 useState 或 NuxtApp 的 payload。const nuxtApp = useNuxtApp()nuxtApp.payload.xxx = data。下一个中间件或页面可以读取。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个命名中间件 guest.ts,只允许未登录用户访问(已登录跳转首页),应用到 /login 页面
  2. 进阶题(难度⭐⭐):实现完整的权限体系:全局 auth 检查登录 + 命名 admin 检查角色,/admin/** 路由受保护
  3. 挑战题(难度⭐⭐⭐):实现中间件间的数据传递——在 auth 中间件获取用户信息存入 NuxtApp payload,在 admin 中间件直接读取,避免重复请求

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏