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

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

Nuxt 提供一个可自定义的路由中间件（route middleware）框架，可在整个应用中使用，适合提取你希望在导航到特定路由之前运行的代码。

路由中间件有三种类型：

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

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

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

::note
只有目录顶层的文件（或任意子目录中的 index 文件）会被注册。index 文件会使用其所在文件夹的名称：`middleware/auth/index.ts` 会被注册为 `auth`。
::

::note
路由中间件在 Nuxt 应用的 Vue 部分中运行。尽管名称相似，它们与在应用 Nitro 服务器部分运行的[服务器中间件](https://nuxt.zhcndoc.com/docs/4.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/4.x/api/utils/navigate-to) - 重定向到给定路由
2. [`abortNavigation`](https://nuxt.zhcndoc.com/docs/4.x/api/utils/abort-navigation) - 取消导航，可选提供错误消息。

与 `vue-router` 的[导航守卫](https://router.vuejs.org/guide/advanced/navigation-guards#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/4.x/api/utils/navigate-to"}
::

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

::important
我们建议使用上面的辅助函数来执行重定向或停止导航。其他在 [vue-router 文档](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards) 中描述的可能返回值可能也能工作，但在将来可能会有破坏性变更。
::

## 中间件执行顺序

中间件按以下顺序运行：

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

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

```bash [app/middleware/ directory]
-| 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 [Directory structure]
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
```

::note
如果你不熟悉“按字母顺序”编号，请记住文件名是作为字符串排序的，而不是作为数值。例如，`10.new.global.ts` 会排在 `2.new.global.ts` 之前。这就是示例中在单个数字前加 `0` 的原因。
::

## 中间件何时运行

如果你的网站是服务器渲染或生成的，初始页面的中间件会在页面渲染时执行一次，然后在客户端再次执行一次。如果你的中间件需要浏览器环境（例如你有一个生成的站点、对响应进行强缓存，或想从 localStorage 读取值），这可能是需要的。

但是，如果你想避免这种行为，可以这样做：

```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/4.x/getting-started/error-handling#useerror) 来检查是否正在处理错误。
::

## 在中间件中访问路由

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

::warning
有时你可能会调用一个内部使用了 `useRoute()` 的组合式函数，即使中间件中没有直接调用 `useRoute()` 也会触发此警告。
这会导致与上文相同的问题，因此当这些函数在中间件中使用时，应将路由作为参数传入以重构它们。
::

::code-group
```ts twoslash [middleware/access-route.ts]
// @errors: 2304
export default defineNuxtRouteMiddleware((to) => {
  // 将路由传递给函数以避免在中间件中调用 `useRoute()`
  doSomethingWithRoute(to)

  // ❌ 这会输出警告，不推荐这样做
  callsRouteInternally()
})
```

```ts twoslash [utils/handle-route.ts]
// 将路由作为参数提供，以便在中间件中正确使用
export function doSomethingWithRoute (route = useRoute()) {
  // ...
}
```

```ts twoslash [utils/dont-do-this.ts]
// ❌ 这个函数不适合在中间件中使用
export function callsRouteInternally () {
  const route = useRoute()
  // ...
}
```
::

## 动态添加中间件

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

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

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

## 示例

```bash [Directory Structure]
-| 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 (/* 某些条件 */ Math.random() > 0.5) {
            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.
