Menu

proxy.js

注意middleware 文件约定已被弃用并重命名为 proxy。更多详情请参阅迁移到 Proxy

proxy.js|ts 文件用于编写 Proxy 并在请求完成之前在服务器上运行代码。然后,根据传入的请求,你可以通过重写、重定向、修改请求或响应头,或直接响应来修改响应。

Proxy 在路由渲染之前执行。它特别适用于实现自定义服务器端逻辑,如身份验证、日志记录或处理重定向。

值得注意的是

Proxy 旨在与你的渲染代码分离调用,并在优化情况下部署到你的 CDN 以实现快速重定向/重写处理,你不应尝试依赖共享模块或全局变量。

要将信息从 Proxy 传递到你的应用程序,请使用 headerscookiesrewritesredirects 或 URL。

在项目根目录中创建一个 proxy.ts(或 .js)文件,或者如果适用,在 src 内创建,使其与 pagesapp 位于同一级别。

如果你自定义了 pageExtensions,例如改为 .page.ts.page.js,请相应地将文件命名为 proxy.page.tsproxy.page.js

proxy.ts
TypeScript
import { NextResponse, NextRequest } from 'next/server'
 
// 如果在内部使用 `await`,此函数可以标记为 `async`
export function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}
 
export const config = {
  matcher: '/about/:path*',
}

导出

Proxy 函数

该文件必须导出一个单独的函数,可以作为默认导出或命名为 proxy。注意,不支持从同一文件导出多个 proxy。

proxy.js
// 默认导出示例
export default function proxy(request) {
  // Proxy 逻辑
}

Config 对象(可选)

可选地,可以在 Proxy 函数旁边导出一个 config 对象。该对象包括 matcher,用于指定 Proxy 应用的路径。

Matcher

matcher 选项允许你针对特定路径运行 Proxy。你可以通过多种方式指定这些路径:

  • 对于单个路径:直接使用字符串定义路径,如 '/about'
  • 对于多个路径:使用数组列出多个路径,例如 matcher: ['/about', '/contact'],将 Proxy 应用于 /about/contact
proxy.js
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

此外,matcher 选项通过正则表达式支持复杂的路径规范,例如 matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],可以精确控制要包含或排除的路径。

matcher 选项接受具有以下键的对象数组:

  • source:用于匹配请求路径的路径或模式。它可以是用于直接路径匹配的字符串,也可以是用于更复杂匹配的模式。
  • regexp(可选):基于 source 微调匹配的正则表达式字符串。它提供对包含或排除哪些路径的额外控制。
  • locale(可选):布尔值,当设置为 false 时,在路径匹配中忽略基于区域设置的路由。
  • has(可选):根据特定请求元素(如 headers、查询参数或 cookies)的存在指定条件。
  • missing(可选):关注某些请求元素不存在的条件,如缺少 headers 或 cookies。
proxy.js
export const config = {
  matcher: [
    {
      source: '/api/*',
      regexp: '^/api/(.*)',
      locale: false,
      has: [
        { type: 'header', key: 'Authorization', value: 'Bearer Token' },
        { type: 'query', key: 'userId', value: '123' },
      ],
      missing: [{ type: 'cookie', key: 'session', value: 'active' }],
    },
  ],
}

配置的 matchers:

  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 文档的详细信息。

值得注意的是

  • matcher 值需要是常量,以便在构建时进行静态分析。动态值(如变量)将被忽略。
  • 为了向后兼容,Next.js 始终将 /public 视为 /public/index。因此,/public/:path 的 matcher 将匹配。

参数

request

定义 Proxy 时,默认导出函数接受单个参数 request。此参数是 NextRequest 的实例,表示传入的 HTTP 请求。

proxy.ts
TypeScript
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // Proxy 逻辑在这里
}

值得注意的是

  • NextRequest 是一个类型,表示 Next.js Proxy 中的传入 HTTP 请求,而 NextResponse 是一个用于操作和发送回 HTTP 响应的类。

NextResponse

NextResponse API 允许你:

  • redirect 将传入请求重定向到不同的 URL
  • rewrite 通过显示给定的 URL 来重写响应
  • 为 API Routes、getServerSidePropsrewrite 目标设置请求头
  • 设置响应 cookies
  • 设置响应头

要从 Proxy 生成响应,你可以:

  1. rewrite 到生成响应的路由(PageRoute Handler
  2. 直接返回 NextResponse。参见生成响应

值得注意的是:对于重定向,你也可以使用 Response.redirect 代替 NextResponse.redirect

执行顺序

Proxy 将为项目中的每个路由调用。鉴于此,使用 matchers 精确定位或排除特定路由至关重要。以下是执行顺序:

  1. next.config.js 中的 headers
  2. next.config.js 中的 redirects
  3. Proxy(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

Runtime

Proxy 默认使用 Node.js 运行时。runtime 配置选项在 Proxy 文件中不可用。在 Proxy 中设置 runtime 配置选项将抛出错误。

高级 Proxy 标志

在 Next.js 的 v13.1 中,为 proxy 引入了两个额外的标志,skipMiddlewareUrlNormalizeskipTrailingSlashRedirect,用于处理高级用例。

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

next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
proxy.js
const legacyPrefixes = ['/docs', '/blog']
 
export default async function proxy(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,
}
proxy.js
export default async function proxy(req) {
  const { pathname } = req.nextUrl
 
  // GET /_next/data/build-id/hello.json
 
  console.log(pathname)
  // 使用此标志,现在是 /_next/data/build-id/hello.json
  // 没有此标志,将被标准化为 /hello
}

示例

条件语句

proxy.ts
TypeScript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(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))
  }
}

使用 Cookies

Cookies 是常规的 headers。在 Request 中,它们存储在 Cookie header 中。在 Response 中,它们在 Set-Cookie header 中。Next.js 通过 NextRequestNextResponse 上的 cookies 扩展提供了一种方便的方式来访问和操作这些 cookies。

  1. 对于传入请求,cookies 具有以下方法:getgetAllsetdelete cookies。你可以使用 has 检查 cookie 的存在,或使用 clear 删除所有 cookies。
  2. 对于传出响应,cookies 具有以下方法:getgetAllsetdelete
proxy.ts
TypeScript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // 假设传入请求中存在 "Cookie:nextjs=fast" header
  // 使用 `RequestCookies` API 从请求中获取 cookies
  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 在响应上设置 cookies
  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=/` header。
 
  return response
}

设置 Headers

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

proxy.ts
TypeScript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // 克隆请求头并设置新的 header `x-hello-from-proxy1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-proxy1', 'hello')
 
  // 你也可以在 NextResponse.next 中设置请求头
  const response = NextResponse.next({
    request: {
      // 新的请求头
      headers: requestHeaders,
    },
  })
 
  // 设置新的响应头 `x-hello-from-proxy2`
  response.headers.set('x-hello-from-proxy2', 'hello')
  return response
}

注意,代码片段使用:

  • NextResponse.next({ request: { headers: requestHeaders } }) 使 requestHeaders 在上游可用
  • 不是 NextResponse.next({ headers: requestHeaders }),后者使 requestHeaders 对客户端可用

NextResponse headers in Proxy 中了解更多。

值得注意的是:避免设置过大的 headers,因为根据你的后端 Web 服务器配置,可能会导致 431 Request Header Fields Too Large 错误。

CORS

你可以在 Proxy 中设置 CORS headers 以允许跨源请求,包括简单预检请求。

proxy.ts
TypeScript
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 proxy(request: NextRequest) {
  // 从请求中检查 origin
  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*',
}

值得注意的是:你可以在 Route Handlers 中为单个路由配置 CORS headers。

生成响应

你可以通过返回 ResponseNextResponse 实例直接从 Proxy 响应。(这从 Next.js v13.1.0 开始可用)

proxy.ts
TypeScript
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
 
// 将 proxy 限制在以 `/api/` 开头的路径
export const config = {
  matcher: '/api/:function*',
}
 
export function proxy(request: NextRequest) {
  // 调用我们的身份验证函数来检查请求
  if (!isAuthenticated(request)) {
    // 使用 JSON 响应表示错误消息
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

负向匹配

matcher 配置允许完整的正则表达式,因此支持负向前瞻或字符匹配等匹配。这里可以看到一个负向前瞻的示例,用于匹配除特定路径之外的所有路径:

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

你还可以通过使用 missinghas 数组,或两者的组合,为某些请求绕过 Proxy:

proxy.js
export const config = {
  matcher: [
    /*
     * 匹配所有请求路径,除了以以下内容开头的路径:
     * - api(API routes)
     * - _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' }],
    },
  ],
}

waitUntilNextFetchEvent

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

waitUntil() 方法接受一个 promise 作为参数,并延长 Proxy 的生命周期直到 promise 完成。这对于在后台执行工作很有用。

proxy.ts
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
 
export function proxy(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 15.1 开始,next/experimental/testing/server 包包含用于帮助单元测试 proxy 文件的实用工具。单元测试 proxy 可以帮助确保它仅在所需路径上运行,并且自定义路由逻辑在代码到达生产环境之前按预期工作。

unstable_doesProxyMatch 函数可用于断言 proxy 是否将针对提供的 URL、headers 和 cookies 运行。

import { unstable_doesProxyMatch } from 'next/experimental/testing/server'
 
expect(
  unstable_doesProxyMatch({
    config,
    nextConfig,
    url: '/test',
  })
).toEqual(false)

整个 proxy 函数也可以进行测试。

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

平台支持

部署选项支持
Node.js server
Docker container
Static export
Adapters平台特定

了解如何在自托管 Next.js 时配置 Proxy

迁移到 Proxy

为什么要改变

重命名 middleware 的原因是术语"middleware"经常与 Express.js middleware 混淆,导致对其目的的误解。此外,Middleware 功能强大,可能会鼓励使用;然而,建议将此功能作为最后的手段使用。

Next.js 正在向前发展,提供具有更好人体工程学的更好 API,以便开发人员可以在不使用 Middleware 的情况下实现他们的目标。这就是重命名 middleware 的原因。

为什么是"Proxy"

名称 Proxy 阐明了 Middleware 的功能。术语"proxy"意味着它在应用程序前面有一个网络边界,这正是 Middleware 的行为。此外,Middleware 默认在 Edge Runtime 运行,它可以在更靠近客户端的位置运行,与应用程序的区域分离。这些行为与术语"proxy"更一致,并提供了该功能更清晰的目的。

如何迁移

我们建议用户避免依赖 Middleware,除非没有其他选择。我们的目标是为他们提供具有更好人体工程学的 API,以便他们可以在不使用 Middleware 的情况下实现他们的目标。

术语"middleware"经常让用户与 Express.js middleware 混淆,这可能会鼓励误用。为了阐明我们的方向,我们将文件约定重命名为"proxy"。这突出表明我们正在远离 Middleware,拆分其过载的功能,并使 Proxy 的目的更加明确。

Next.js 提供了一个 codemod 来从 middleware.ts 迁移到 proxy.ts。你可以运行以下命令进行迁移:

npx @next/codemod@canary middleware-to-proxy .

该 codemod 将重命名文件和函数名称,从 middleware 改为 proxy

// middleware.ts -> proxy.ts
 
- export function middleware() {
+ export function proxy() {

版本历史

版本变更
v16.0.0Middleware 已弃用并重命名为 Proxy
v15.5.0Middleware 现在可以使用 Node.js runtime(稳定)
v15.2.0Middleware 现在可以使用 Node.js runtime(实验性)
v13.1.0引入高级 Middleware 标志
v13.0.0Middleware 可以修改请求头、响应头并发送响应
v12.2.0Middleware 稳定,请参阅升级指南
v12.0.9在 Edge Runtime 中强制使用绝对 URLs(PR
v12.0.0引入 Middleware(Beta)