中间件 (Middleware)

中间件 (Middleware) 允许您在请求完成前运行代码。然后根据传入的请求,您可以通过重写、重定向、修改请求或响应头,或直接响应来修改响应。

中间件 (Middleware) 会在缓存内容和路由匹配之前运行。详见路径匹配

使用场景

中间件 (Middleware) 特别适用的常见场景包括:

  • 读取部分传入请求后快速重定向
  • 基于 A/B 测试或实验重写到不同页面
  • 修改所有页面或部分页面的头部信息

中间件 (Middleware) 适合以下场景:

  • 缓慢的数据获取
  • 会话管理

约定

在项目根目录下使用 middleware.ts(或 .js)文件定义中间件 (Middleware)。例如,与 pagesapp 同级,或在 src 目录内(如适用)。

注意:虽然每个项目仅支持一个 middleware.ts 文件,但您仍可以模块化组织中间件逻辑。将中间件功能拆分为单独的 .ts.js 文件,然后导入到主 middleware.ts 文件中。这样可以更清晰地管理特定路由的中间件,同时在 middleware.ts 中集中控制。通过强制使用单一中间件文件,可以简化配置、避免潜在冲突,并通过避免多层中间件来优化性能。

示例

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

// 如果内部使用 `await`,此函数可标记为 `async`
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}

// 详见下方的"路径匹配"部分
export const config = {
  matcher: '/about/:path*',
}

路径匹配

中间件 (Middleware) 会为项目中的每个路由调用。因此,使用匹配器 (matcher) 精确指定或排除特定路由至关重要。以下是执行顺序:

  1. next.config.js 中的 headers
  2. next.config.js 中的 redirects
  3. 中间件 (Middleware)(rewritesredirects 等)
  4. next.config.js 中的 beforeFilesrewrites
  5. 文件系统路由(public/_next/static/pages/app/ 等)
  6. next.config.js 中的 afterFilesrewrites
  7. 动态路由(/blog/[slug]
  8. next.config.js 中的 fallbackrewrites

有两种方式定义中间件 (Middleware) 运行的路径:

  1. 自定义匹配器配置
  2. 条件语句

匹配器 (Matcher)

matcher 允许您筛选中间件 (Middleware) 在特定路径上运行。

middleware.js
export const config = {
  matcher: '/about/:path*',
}

您可以使用数组语法匹配单个路径或多个路径:

middleware.js
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

matcher 配置支持完整正则表达式,因此支持负向先行断言或字符匹配等操作。以下是匹配除特定路径外的所有路径的负向先行断言示例:

middleware.js
export const config = {
  matcher: [
    /*
     * 匹配所有请求路径,除了以以下内容开头的路径:
     * - api(API 路由)
     * - _next/static(静态文件)
     * - _next/image(图片优化文件)
     * - favicon.ico、sitemap.xml、robots.txt(元数据文件)
     */
    '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
  ],
}

您还可以使用 missinghas 数组,或两者的组合,来绕过某些请求的中间件 (Middleware):

middleware.js
export const config = {
  matcher: [
    /*
     * 匹配所有请求路径,除了以以下内容开头的路径:
     * - api(API 路由)
     * - _next/static(静态文件)
     * - _next/image(图片优化文件)
     * - favicon.ico、sitemap.xml、robots.txt(元数据文件)
     */
    {
      source:
        '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },

    {
      source:
        '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
      has: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },

    {
      source:
        '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
      has: [{ type: 'header', key: 'x-present' }],
      missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
    },
  ],
}

须知matcher 值必须是常量,以便在构建时进行静态分析。动态值(如变量)将被忽略。

配置的匹配器:

  1. 必须以 / 开头
  2. 可以包含命名参数:/about/:path 匹配 /about/a/about/b,但不匹配 /about/a/c
  3. 可以在命名参数上使用修饰符(以 : 开头):/about/:path* 匹配 /about/a/b/c,因为 * 表示 零个或多个? 表示 零个或一个+ 表示 一个或多个
  4. 可以使用括号括起来的正则表达式:/about/(.*) 等同于 /about/:path*

更多详情请参阅 path-to-regexp 文档。

须知:为了向后兼容,Next.js 始终将 /public 视为 /public/index。因此,/public/:path 的匹配器会匹配。

条件语句

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    return NextResponse.rewrite(new URL('/about-2', request.url))
  }

  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.rewrite(new URL('/dashboard/user', request.url))
  }
}

NextResponse

NextResponse API 允许您:

  • 将传入请求 redirect 到不同的 URL
  • 通过显示给定 URL 来 rewrite 响应
  • 为 API 路由、getServerSidePropsrewrite 目标设置请求头
  • 设置响应 cookie
  • 设置响应头

要从中间件 (Middleware) 生成响应,您可以:

  1. rewrite 到生成响应的路由(页面路由处理器
  2. 直接返回 NextResponse。参见生成响应

Cookie 是常规的头部信息。在 Request 中,它们存储在 Cookie 头中。在 Response 中,它们存储在 Set-Cookie 头中。Next.js 通过 NextRequestNextResponse 上的 cookies 扩展提供了便捷的访问和操作方式。

  1. 对于传入请求,cookies 提供以下方法:getgetAllsetdelete cookie。您可以使用 has 检查 cookie 是否存在,或使用 clear 删除所有 cookie。
  2. 对于传出响应,cookies 提供以下方法:getgetAllsetdelete
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 假设传入请求中存在 "Cookie:nextjs=fast" 头
  // 使用 `RequestCookies` API 从请求中获取 cookie
  let cookie = request.cookies.get('nextjs')
  console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
  const allCookies = request.cookies.getAll()
  console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]

  request.cookies.has('nextjs') // => true
  request.cookies.delete('nextjs')
  request.cookies.has('nextjs') // => false

  // 使用 `ResponseCookies` API 在响应上设置 cookie
  const response = NextResponse.next()
  response.cookies.set('vercel', 'fast')
  response.cookies.set({
    name: 'vercel',
    value: 'fast',
    path: '/',
  })
  cookie = response.cookies.get('vercel')
  console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
  // 传出响应将包含 `Set-Cookie:vercel=fast;path=/` 头。

  return response
}

设置头部

您可以使用 NextResponse API 设置请求和响应头(自 Next.js v13.0.0 起支持设置 请求 头)。

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 克隆请求头并设置新头 `x-hello-from-middleware1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-middleware1', 'hello')

  // 您也可以在 NextResponse.next 中设置请求头
  const response = NextResponse.next({
    request: {
      // 新请求头
      headers: requestHeaders,
    },
  })

  // 设置新响应头 `x-hello-from-middleware2`
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}

须知:避免设置过大的头部,因为这可能会根据后端 Web 服务器配置导致 431 请求头字段过大 错误。

CORS

您可以在中间件 (Middleware) 中设置 CORS 头以允许跨域请求,包括简单请求预检请求

import { NextRequest, NextResponse } from 'next/server'

const allowedOrigins = ['https://acme.com', 'https://my-app.org']

const corsOptions = {
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}

export function middleware(request: NextRequest) {
  // 检查请求中的来源
  const origin = request.headers.get('origin') ?? ''
  const isAllowedOrigin = allowedOrigins.includes(origin)

  // 处理预检请求
  const isPreflight = request.method === 'OPTIONS'

  if (isPreflight) {
    const preflightHeaders = {
      ...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
      ...corsOptions,
    }
    return NextResponse.json({}, { headers: preflightHeaders })
  }

  // 处理简单请求
  const response = NextResponse.next()

  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin)
  }

  Object.entries(corsOptions).forEach(([key, value]) => {
    response.headers.set(key, value)
  })

  return response
}

export const config = {
  matcher: '/api/:path*',
}

须知:您可以在路由处理器中为单个路由配置 CORS 头。

生成响应

您可以直接从中间件返回一个 ResponseNextResponse 实例来生成响应。(此功能自 Next.js v13.1.0 起可用)

import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// 将中间件限制为以 `/api/` 开头的路径
export const config = {
  matcher: '/api/:function*',
}

export function middleware(request: NextRequest) {
  // 调用身份验证函数检查请求
  if (!isAuthenticated(request)) {
    // 返回包含错误信息的 JSON 响应
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

waitUntilNextFetchEvent

NextFetchEvent 对象扩展了原生的 FetchEvent 对象,并包含 waitUntil() 方法。

waitUntil() 方法接收一个 Promise 作为参数,并延长中间件的生命周期直到该 Promise 完成。这对于在后台执行工作非常有用。

middleware.ts
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'

export function middleware(req: NextRequest, event: NextFetchEvent) {
  event.waitUntil(
    fetch('https://my-analytics-platform.com', {
      method: 'POST',
      body: JSON.stringify({ pathname: req.nextUrl.pathname }),
    })
  )

  return NextResponse.next()
}

高级中间件标志

在 Next.js 的 v13.1 版本中,引入了两个额外的中间件标志 skipMiddlewareUrlNormalizeskipTrailingSlashRedirect 来处理高级用例。

skipTrailingSlashRedirect 禁用了 Next.js 用于添加或移除尾部斜杠的重定向。这允许在中间件内进行自定义处理,为某些路径保留尾部斜杠而不为其他路径保留,从而使得增量迁移更加容易。

next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
middleware.js
const legacyPrefixes = ['/docs', '/blog']

export default async function middleware(req) {
  const { pathname } = req.nextUrl

  if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
    return NextResponse.next()
  }

  // 应用尾部斜杠处理
  if (
    !pathname.endsWith('/') &&
    !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
  ) {
    return NextResponse.redirect(
      new URL(`${req.nextUrl.pathname}/`, req.nextUrl)
    )
  }
}

skipMiddlewareUrlNormalize 允许禁用 Next.js 中的 URL 规范化,使得直接访问和客户端跳转的处理方式一致。在某些高级用例中,此选项通过使用原始 URL 提供了完全控制。

next.config.js
module.exports = {
  skipMiddlewareUrlNormalize: true,
}
middleware.js
export default async function middleware(req) {
  const { pathname } = req.nextUrl

  // GET /_next/data/build-id/hello.json

  console.log(pathname)
  // 启用标志后,pathname 为 /_next/data/build-id/hello.json
  // 未启用标志时,pathname 会被规范化为 /hello
}

单元测试(实验性功能)

从 Next.js 15.1 开始,next/experimental/testing/server 包提供了用于单元测试中间件文件的工具。单元测试中间件有助于确保它仅在所需的路径上运行,并且自定义的路由逻辑在生产环境之前按预期工作。

unstable_doesMiddlewareMatch 函数可用于断言中间件是否会针对提供的 URL、标头和 Cookie 运行。

import { unstable_doesMiddlewareMatch } from 'next/experimental/testing/server'

expect(
  unstable_doesMiddlewareMatch({
    config,
    nextConfig,
    url: '/test',
  })
).toEqual(false)

也可以测试整个中间件函数。

import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'

const request = new NextRequest('https://nextjs.org/docs')
const response = await middleware(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// 如果响应是重定向,也可以使用 getRedirectUrl

运行时

中间件默认使用 Edge 运行时。从 v15.2(canary 版本)开始,我们实验性地支持使用 Node.js 运行时。要启用此功能,请在 next.config 文件中添加标志:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    nodeMiddleware: true,
  },
}

export default nextConfig

然后在中间件文件中,将 config 对象中的运行时设置为 nodejs

export const config = {
  runtime: 'nodejs',
}

注意:此功能尚不建议在生产环境中使用。因此,除非您使用的是 next@canary 版本而非稳定版本,否则 Next.js 会抛出错误。

平台支持

部署选项是否支持
Node.js 服务器
Docker 容器
静态导出
适配器取决于具体平台

了解如何配置中间件以自托管 Next.js。

版本历史

版本变更
v15.2.0中间件现在可以使用 Node.js 运行时(实验性功能)
v13.1.0新增高级中间件标志
v13.0.0中间件可以修改请求标头、响应标头并发送响应
v12.2.0中间件功能稳定,请参阅升级指南
v12.0.9在 Edge 运行时中强制使用绝对 URL (PR)
v12.0.0新增中间件(Beta 版)

On this page