---
title: "middleware"
description: "Nuxt 提供中间件以在导航到特定路由之前运行代码。"
canonical_url: "https://nuxt.zhcndoc.com/docs/3.x/directory-structure/middleware"
---
# middleware

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

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

路由中间件有三种类型：

1. 匿名（或内联）路由中间件，直接在页面内定义。
2. 命名路由中间件，放置在 `middleware/` 目录下，并在页面使用时通过异步导入自动加载。
3. 全局路由中间件，放置在带有 `.global` 后缀的 `middleware/` 目录下，会在每次路由变更时执行。

前两种路由中间件可以在 [`definePageMeta`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/define-page-meta) 中定义。

::note
中间件名称会被规范化为 kebab-case：`myMiddleware` 变成 `my-middleware`。
::

::note
路由中间件在 Nuxt 应用的 Vue 部分中运行。尽管名称相似，它们与在应用 Nitro 服务器部分运行的[服务器中间件](https://nuxt.zhcndoc.com/docs/3.x/directory-structure/server#server-middleware)完全不同。
::

::video-accordion{title="观看 Vue School 关于三种中间件的视频教程" videoId="761471577" platform="vimeo"}
::

## 用法

路由中间件是导航守卫，接收当前路由和目标路由作为参数。

```ts twoslash [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`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/navigate-to) - 重定向到给定路由
2. [`abortNavigation`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/abort-navigation) - 中止导航，可选地附带错误消息。

与 `vue-router` 的[导航守卫](https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards)不同，中间件不会传入第三个 `next()` 参数，**重定向或取消路由通过返回值处理**。

可能的返回值包括：

- 什么都不返回（简单的 `return` 或没有返回）- 不阻塞导航，将执行下一个中间件（如果有）或完成路由导航
- `return navigateTo('/')` - 重定向到指定路径，若在服务器端执行重定向，则状态码为 [`302` Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/302)
- `return navigateTo('/', { redirectCode: 301 })` - 重定向到指定路径，若在服务器端执行重定向，则状态码为 [`301` Moved Permanently](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301)
- `return abortNavigation()` - 停止当前导航
- `return abortNavigation(error)` - 拒绝当前导航，并带有错误

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/utils/navigate-to"}
::

::read-more{to="https://nuxt.zhcndoc.com/docs/3.x/api/utils/abort-navigation"}
::

::important
我们推荐使用上述辅助函数来执行重定向或停止导航。其他在 [vue-router 文档](https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards)中描述的返回值可能有效，但未来存在破坏性变更的风险。
::

## 中间件执行顺序

中间件的执行顺序为：

1. 全局中间件
2. 页面定义的中间件顺序（若使用数组语法声明多个中间件）

例如，假设您有如下中间件和组件：

```bash [middleware/ 目录]
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
```

```vue twoslash [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` 之前执行。这时建议给全局中间件文件名添加“字母顺序”数字前缀。

```bash [目录结构]
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
```

::note
如果您不熟悉“字母顺序”排序，请记住文件名是作为字符串排序，而非数字大小。例如，`10.new.global.ts` 会排在 `2.new.global.ts` 之前。这就是示例中单数字前缀带 `0` 的原因。
::

## 中间件何时运行

如果您的站点是服务端渲染或静态生成，中间件会在页面首次渲染时执行，然后在客户端再执行一次。如果中间件依赖于浏览器环境（如生成的站点，或缓存响应，或要读取本地存储），这很有用。

但若您想避免此行为，可以这样写：

```ts twoslash [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
  }
})
```

即便您在服务器端中间件中抛出了错误并渲染了错误页，浏览器端仍会再次执行该中间件。

::note
渲染错误页属于完全独立的页面加载，这意味着任何已注册的中间件都会再次运行。您可以在中间件中使用 [`useError`](https://nuxt.zhcndoc.com/docs/3.x/getting-started/error-handling#useerror) 检查是否正在处理错误。
::

## 动态添加中间件

始终在中间件中使用 `to` 和 `from` 参数来访问目标路由和前一个路由。完全避免在此上下文中使用 [`useRoute()`](https://nuxt.zhcndoc.com/docs/3.x/api/composables/use-route) 组合式函数。
**中间件中没有“当前路由”的概念**，因为中间件可以中止导航或重定向到其他路由。在此上下文中，`useRoute()` 组合式函数始终会返回不准确的信息。

::warning
有时，您可能会调用内部使用 `useRoute()` 的组合式函数，即使中间件中没有直接调用它，也可能触发此警告。
这会导致**与上述相同的问题**，因此在中间件中使用这些函数时，您应该调整函数结构，让它们接受路由作为参数。
::

::code-group
```ts twoslash [middleware/access-route.ts]
// @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()
})
```

```ts twoslash [utils/handle-route.ts]
// providing the route as an argument so that it can be used in middleware correctly
export function doSomethingWithRoute (route = useRoute()) {
  // ...
}
```

```ts twoslash [utils/dont-do-this.ts]
// ❌ this function is not suitable for use in middleware
export function callsRouteInternally () {
  const route = useRoute()
  // ...
}
```
::

## 动态添加中间件

您可以使用 [`addRouteMiddleware()`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/add-route-middleware) 辅助函数手动添加全局或命名路由中间件，例如在插件中添加。

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

  addRouteMiddleware('named-test', () => {
    console.log('此命名中间件是在插件中添加的，会覆盖同名已存在中间件')
  })
})
```

## 示例

```bash [目录结构]
-| middleware/
---| auth.ts
```

在页面文件中，您可以引用该路由中间件：

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

导航到该页面前，将执行 `auth` 路由中间件。

::link-example{to="https://nuxt.zhcndoc.com/docs/examples/routing/middleware"}
::

## 在构建时设置中间件

您可以不在每个页面中使用 `definePageMeta`，而是在 `pages:extend` 钩子中添加命名路由中间件。

```ts twoslash [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)
    },
  },
})
```


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
