middleware

Nuxt 提供了中间件功能,用于在导航到特定路由之前运行代码。

Nuxt 提供了一个可自定义的 路由中间件 框架,你可以在整个应用中使用它,非常适合提取你希望在导航到特定路由之前运行的代码。

路由中间件有三种类型:

  1. 匿名(或内联)路由中间件,直接在页面中定义。
  2. 命名路由中间件,放置在 middleware/ 目录中,并在页面使用时通过异步导入自动加载。
  3. 全局路由中间件,放置在 middleware/ 目录中并以 .global 后缀命名,每次路由变更时都会运行。

前两种路由中间件可以通过 definePageMeta 定义。

中间件名称会被规范化为 kebab-case,例如 myMiddleware 会变为 my-middleware
路由中间件运行在 Nuxt 应用的 Vue 部分。尽管名称相似,但它们与运行在 Nitro 服务器部分的服务器中间件完全不同。

使用方法

路由中间件是导航守卫,接收当前路由和下一路由作为参数。

middleware/my-middleware.ts
export default defineNuxtRouteMiddleware((to, from) => {
  if (to.params.id === '1') {
    return abortNavigation()
  }
  // 在实际应用中,你可能不会将每个路由重定向到 `/`
  // 但在重定向之前检查 `to.path` 很重要,否则可能导致无限重定向循环
  if (to.path !== '/') {
    return navigateTo('/')
  }
})

Nuxt 提供了两个全局可用的辅助函数,可以直接从中间件返回:

  1. navigateTo - 重定向到指定路由
  2. abortNavigation - 中止导航,可选带错误信息

vue-router 的导航守卫 不同,中间件不会传递第三个 next() 参数,重定向或取消路由通过从中间件返回值来处理

可能的返回值包括:

  • 无返回值(简单 return 或不返回任何值) - 不阻止导航,将继续执行下一个中间件(如果有)或完成路由导航
  • return navigateTo('/') - 重定向到指定路径,如果在服务端发生重定向,将设置重定向状态码为 302 Found
  • return navigateTo('/', { redirectCode: 301 }) - 重定向到指定路径,如果在服务端发生重定向,将设置重定向状态码为 301 Moved Permanently
  • return abortNavigation() - 停止当前导航
  • return abortNavigation(error) - 以错误拒绝当前导航
阅读更多 Docs > API > Utils > Navigate To.
阅读更多 Docs > API > Utils > Abort Navigation.
我们建议使用上述辅助函数执行重定向或停止导航。vue-router 文档 中描述的其他可能返回值可能有效,但未来可能会出现破坏性变更。

中间件执行顺序

中间件按以下顺序运行:

  1. 全局中间件
  2. 页面定义的中间件顺序(如果使用数组语法声明了多个中间件)

例如,假设你有以下中间件和组件:

middleware/ directory
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
pages/profile.vue
<script setup lang="ts">
definePageMeta({
  middleware: [
    function (to, from) {
      // 自定义内联中间件
    },
    'auth',
  ],
});
</script>

中间件的执行顺序如下:

  1. analytics.global.ts
  2. setup.global.ts
  3. 自定义内联中间件
  4. auth.ts

全局中间件的排序

默认情况下,全局中间件按文件名字母顺序执行。

但有时你可能需要定义特定的顺序。例如,在上述场景中,setup.global.ts 可能需要先于 analytics.global.ts 运行。在这种情况下,我们建议为全局中间件添加“字母数字”前缀。

Directory structure
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
如果你不熟悉“字母数字”排序,请记住,文件名是按字符串排序,而非数值排序。例如,10.new.global.ts 会排在 2.new.global.ts 之前。这就是示例中对个位数添加 0 前缀的原因。

中间件运行时机

如果你的网站是服务端渲染或生成的,初始页面的中间件将在页面渲染时和服务端运行,然后在客户端再次运行。如果你的中间件需要浏览器环境(例如在生成网站、激进缓存响应或读取本地存储值时),这可能是必要的。

但如果你想避免这种行为,可以这样做:

middleware/example.ts
export default defineNuxtRouteMiddleware(to => {
  // 在服务端跳过中间件
  if (import.meta.server) return
  // 完全在客户端跳过中间件
  if (import.meta.client) return
  // 或仅在客户端初始加载时跳过中间件
  const nuxtApp = useNuxtApp()
  if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return
})

即使你在服务端的中间件中抛出错误并渲染了错误页面,中间件仍会在浏览器中再次运行。

渲染错误页面是一个完全独立的页面加载,这意味着任何注册的中间件将再次运行。你可以在中间件中使用 useError 检查是否正在处理错误。

动态添加中间件

可以使用 addRouteMiddleware() 辅助函数手动添加全局或命名路由中间件,例如在插件中:

export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('此全局中间件在插件中添加,将在每次路由变更时运行')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('此命名中间件在插件中添加,将覆盖同名的任何现有中间件')
  })
})

示例

Directory Structure
-| middleware/
---| auth.ts

在页面文件中,你可以引用此路由中间件:

<script setup lang="ts">
definePageMeta({
  middleware: ["auth"]
  // 或 middleware: 'auth'
})
</script>

现在,在完成导航到该页面之前,将先运行 auth 路由中间件。

阅读并编辑实时示例中的内容 Docs > Examples > Routing > Middleware.

构建时设置中间件

除了在每个页面上使用 definePageMeta,你还可以在 pages:extend 钩子中添加命名路由中间件。

nuxt.config.ts
import type { NuxtPage } from 'nuxt/schema'

export default defineNuxtConfig({
  hooks: {
    'pages:extend' (pages) {
      function setMiddleware (pages: NuxtPage[]) {
        for (const page of pages) {
          if (/* 某些条件 */ true) {
            page.meta ||= {}
            // 注意,这将覆盖页面中 `definePageMeta` 设置的任何中间件
            page.meta.middleware = ['named']
          }
          if (page.children) {
            setMiddleware(page.children)
          }
        }
      }
      setMiddleware(pages)
    }
  }
})