middleware

Nuxt 提供中间件,以便在导航到特定路由之前运行代码。

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

路由中间件共有三种类型:

  1. 匿名(或内联)路由中间件直接在页面内定义。
  2. 具名路由中间件,放置在 app/middleware/ 目录中,在页面中使用时会通过异步导入自动加载。
  3. 全局路由中间件,放置在 app/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()
  }
  // In a real app you would probably not redirect every route to `/`
  // however it is important to check `to.path` before redirecting or you
  // might get an infinite redirect loop
  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) - 拒绝当前导航并抛出错误
阅读更多信息:文档 > 4.x > API > 工具 (Utils) > Navigate To
阅读更多信息:文档 > 4.x > API > 工具 (Utils) > Abort Navigation
我们建议使用上述辅助函数来执行重定向或停止导航。虽然vue-router 文档中描述的其他可能返回值或许也能生效,但未来可能会有破坏性更新。

中间件顺序

中间件按以下顺序运行:

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

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

app/middleware/ 目录
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
pages/profile.vue
<script setup lang="ts">
definePageMeta({
  middleware: [
    function (to, from) {
      // Custom inline middleware
    },
    'auth',
  ],
})
</script>

你可以预期中间件会按以下顺序运行:

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

全局中间件排序

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

但是,有时你可能需要定义特定的顺序。例如,在上面的场景中,可能需要先运行 setup.global.ts,再运行 analytics.global.ts。在这种情况下,我们建议在全局中间件文件名前加上“字母”编号。

目录结构
-| 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) => {
  // skip middleware on server
  if (import.meta.server) {
    return
  }
  // skip middleware on client side entirely
  if (import.meta.client) {
    return
  }
  // or only skip middleware on initial client load
  const nuxtApp = useNuxtApp()
  if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) {
    return
  }
})

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

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

在中间件中访问路由

在中间件中,请始终使用 tofrom 参数来访问目标路由和来源路由。完全避免在此上下文中使用 useRoute() 可组合函数。中间件中不存在“当前路由”的概念,因为中间件可能会中止导航或重定向到不同的路由。useRoute() 在此上下文中始终是不准确的。

有时,你可能会调用一个内部使用了 useRoute() 的可组合函数,即使在你的中间件中没有直接调用,也可能会触发警告。这会导致上述同样的问题,因此当函数在中间件中使用时,你应该将代码结构调整为接收路由作为参数。
// @errors: 2304
export default defineNuxtRouteMiddleware((to) => {
  // passing the route to the function to avoid calling `useRoute()` in middleware
  doSomethingWithRoute(to)

  // ❌ this will output a warning and is NOT recommended
  callsRouteInternally()
})

动态添加中间件

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

export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('this global middleware was added in a plugin and will be run on every route change')
  }, { global: true })

  addRouteMiddleware('named-test', () => {
    console.log('this named middleware was added in a plugin and would override any existing middleware of the same name')
  })
})

示例

目录结构
-| middleware/
---| auth.ts

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

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

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

文档 > 4.x > 示例 > 路由 > 中间件 中阅读并编辑在线示例。

在构建时设置中间件

除了在每个页面使用 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 (/* some condition */ Math.random() > 0.5) {
            page.meta ||= {}
            // Note that this will override any middleware set in `definePageMeta` in the page
            page.meta.middleware = ['named']
          }
          if (page.children) {
            setMiddleware(page.children)
          }
        }
      }
      setMiddleware(pages)
    },
  },
})