中间件

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

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

¥Nuxt provides a customizable route middleware framework you can use throughout your application, ideal for extracting code that you want to run before navigating to a particular route.

路由中间件有三种类型:

¥There are three kinds of route middleware:

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

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

¥The first two kinds of route middleware can be defined in definePageMeta.

中间件名称已规范化为短横线命名:myMiddleware 变为 my-middleware
路由中间件在 Nuxt 应用的 Vue 部分运行。尽管名称相似,但它们与运行在应用 Nitro 服务器部分的 server middleware 完全不同。

用法

¥Usage

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

¥Route middleware are navigation guards that receive the current route and the next route as arguments.

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 提供了两个全局可用的辅助函数,可以直接从中间件返回。

¥Nuxt provides two globally available helpers that can be returned directly from the middleware.

  1. navigateTo - 重定向到指定的路由
  2. abortNavigation - 中止导航,并显示可选的错误消息。

vue-router 中的 导航保护 不同,通用渲染无需传递第三个 next() 参数,重定向或路由取消操作由中间件返回值来处理。

¥Unlike navigation guards from vue-router, a third next() argument is not passed, and redirect or route cancellation is handled by returning a value from the middleware.

可能的返回值包括:

¥Possible return values are:

  • 什么也没有(简单的 return 或根本没有返回) - 不会阻止导航,并将转到下一个中​​间件函数(如果有),或完成路由导航。
  • return navigateTo('/') - 重定向到指定路径,如果重定向发生在服务器端,则将重定向代码设置为 302 已找到
  • return navigateTo('/', { redirectCode: 301 }) - 重定向到指定路径,如果重定向发生在服务器端,则将重定向代码设置为 301 永久移动
  • return abortNavigation() - 停止当前导航
  • return abortNavigation(error) - 拒绝当前导航并返回错误
Read more in Docs > API > Utils > Navigate To.
Read more in Docs > API > Utils > Abort Navigation.
我们建议使用上述辅助函数来执行重定向或停止导航。the vue-router docs 中描述的其他可能的返回值可能有效,但将来可能会带来重大变更。

中间件顺序

¥Middleware Order

中间件按以下顺序运行:

¥Middleware runs in the following order:

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

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

¥For example, assuming you have the following middleware and component:

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>

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

¥You can expect the middleware to be run in the following order:

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

全局中间件排序

¥Ordering Global Middleware

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

¥By default, global middleware is executed alphabetically based on the filename.

然而,有时你可能需要定义特定的顺序。例如,在最后一种情况下,setup.global.ts 可能需要在 analytics.global.ts 之前运行。在这种情况下,我们建议在全局中间件前添加 'alphabetical' 编号。

¥However, there may be times you want to define a specific order. For example, in the last scenario, setup.global.ts may need to run before analytics.global.ts. In that case, we recommend prefixing global middleware with 'alphabetical' numbering.

Directory structure
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
如果你不熟悉 'alphabetical' 编号,请记住文件名按字符串排序,而不是按数值排序。例如,10.new.global.ts 会排在 2.new.global.ts 之前。这就是为什么示例中在单个数字前加上 0 前缀的原因。

中间件运行时

¥When Middleware Runs

如果你的网站是服务端渲染或生成的,则初始页面的中间件将在页面渲染时以及客户端再次执行。如果你的中间件需要浏览器环境,例如,如果你有一个生成的网站、积极缓存响应或想要从本地存储读取值,则可能需要这样做。

¥If your site is server-rendered or generated, middleware for the initial page will be executed both when the page is rendered and then again on the client. This might be needed if your middleware needs a browser environment, such as if you have a generated site, aggressively cache responses, or want to read a value from local storage.

但是,如果你想避免这种情况,可以这样做:

¥However, if you want to avoid this behaviour you can do so:

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
})

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

¥This is true even if you throw an error in your middleware on the server, and an error page is rendered. The middleware will still run again in the browser.

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

动态添加中间件

¥Adding Middleware Dynamically

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

¥It is possible to add global or named route middleware manually using the addRouteMiddleware() helper function, such as from within a plugin.

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')
  })
})

示例

¥Example

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

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

¥In your page file, you can reference this route middleware:

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

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

¥Now, before navigation to that page can complete, the auth route middleware will be run.

Read and edit a live example in Docs > Examples > Routing > Middleware.

在构建时设置中间件

¥Setting Middleware at Build Time

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

¥Instead of using definePageMeta on each page, you can add named route middleware within the pages:extend hook.

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)
    }
  }
})