Lzh on GitHub

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()
  }
  // 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) - 不会阻止导航,并将移动到下一个中间件函数(如果有),或完成路由导航。
  • return navigateTo('/') - 重定向到给定的路径,如果重定向发生在服务器端,则将重定向代码设置为 302 Found
  • return navigateTo('/', { redirectCode: 301 }) - 重定向到给定的路径,如果重定向发生在服务器端,则将重定向代码设置为 301 Moved Permanently
  • return abortNavigation() - 停止当前导航。
  • return abortNavigation(error) - 使用错误拒绝当前导航。
Read more in Docs > API > Utils > Navigate To.
Read more in 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) {
      // Custom inline middleware
    },
    'auth',
  ],
});
</script>

您可以预期中间件将按以下顺序运行:

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

全局中间件排序

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

但是,有时您可能希望定义特定的顺序。例如,在最后一个场景中,setup.global.ts 可能需要在 analytics.global.ts 之前运行。在这种情况下,我们建议为全局中间件添加 “字母” 编号前缀。

Directory structure
-| middleware/
---| 01.backface-visibility.md.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 来检查是否正在处理错误。

动态添加中间件

可以使用 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 路由中间件。

构建时设置中间件

除了在每个页面上使用 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 */ true) {
            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)
    }
  }
})