Skip to main content

中间件

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

¥Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.

中间件在缓存内容和路由匹配之前运行。详细信息请参见 匹配路径

¥Middleware runs before cached content and routes are matched. See Matching Paths for more details.

用例

¥Use Cases

将中间件集成到你的应用中可以显着提高性能、安全性和用户体验。中间件特别有效的一些常见场景包括:

¥Integrating Middleware into your application can lead to significant improvements in performance, security, and user experience. Some common scenarios where Middleware is particularly effective include:

  • 认证与授权:在授予对特定页面或 API 路由的访问权限之前,请确保用户身份并检查会话 cookie。

    ¥Authentication and Authorization: Ensure user identity and check session cookies before granting access to specific pages or API routes.

  • 服务器端重定向:根据某些条件(例如区域设置、用户角色)在服务器级别重定向用户。

    ¥Server-Side Redirects: Redirect users at the server level based on certain conditions (e.g., locale, user role).

  • 路径重写:通过根据请求属性动态重写 API 路由或页面的路径,支持 A/B 测试、功能推出或旧版路径。

    ¥Path Rewriting: Support A/B testing, feature rollouts, or legacy paths by dynamically rewriting paths to API routes or pages based on request properties.

  • 机器人检测:通过检测和阻止机器人流量来保护你的资源。

    ¥Bot Detection: Protect your resources by detecting and blocking bot traffic.

  • 日志记录和分析:在由页面或 API 处理之前,捕获并分析请求数据以获取见解。

    ¥Logging and Analytics: Capture and analyze request data for insights before processing by the page or API.

  • 功能标记:动态启用或禁用功能以实现无缝功能部署或测试。

    ¥Feature Flagging: Enable or disable features dynamically for seamless feature rollouts or testing.

认识到中间件可能不是最佳方法的情况也同样重要。以下是一些需要注意的场景:

¥Recognizing situations where middleware may not be the optimal approach is just as crucial. Here are some scenarios to be mindful of:

  • 复杂的数据获取和操作:中间件不是为直接数据获取或操作而设计的,这应该在路由处理程序或服务器端实用程序中完成。

    ¥Complex Data Fetching and Manipulation: Middleware is not designed for direct data fetching or manipulation, this should be done within Route Handlers or server-side utilities instead.

  • 繁重的计算任务:中间件应该是轻量级的并且响应快速,否则可能会导致页面加载延迟。繁重的计算任务或长时间运行的进程应在专用的路由处理程序中完成。

    ¥Heavy Computational Tasks: Middleware should be lightweight and respond quickly or it can cause delays in page load. Heavy computational tasks or long-running processes should be done within dedicated Route Handlers.

  • 广泛的会话管理:虽然中间件可以管理基本会话任务,但广泛的会话管理应由专用身份验证服务或在路由处理程序内管理。

    ¥Extensive Session Management: While Middleware can manage basic session tasks, extensive session management should be managed by dedicated authentication services or within Route Handlers.

  • 直接数据库操作:不建议在中间件中执行直接数据库操作。数据库交互应在路由处理程序或服务器端实用程序中完成。

    ¥Direct Database Operations: Performing direct database operations within Middleware is not recommended. Database interactions should done within Route Handlers or server-side utilities.

惯例

¥Convention

使用项目根目录中的文件 middleware.ts(或 .js)来定义中间件。例如,与 pagesapp 处于同一级别,或者在 src 内部(如果适用)。

¥Use the file middleware.ts (or .js) in the root of your project to define Middleware. For example, at the same level as pages or app, or inside src if applicable.

注意:虽然每个项目仅支持一个 middleware.ts 文件,但你仍然可以模块化地组织中间件逻辑。将中间件函数分解为单独的 .ts.js 文件,并将它们导入到主 middleware.ts 文件中。这样可以对特定于路由的中间件进行更清晰的管理,这些中间件聚合在 middleware.ts 中以进行集中控制。通过强制执行单个中间件文件,它可以简化配置,防止潜在冲突,并通过避免多个中间件层来优化性能。

¥Note: While only one middleware.ts file is supported per project, you can still organize your middleware logic modularly. Break out middleware functionalities into separate .ts or .js files and import them into your main middleware.ts file. This allows for cleaner management of route-specific middleware, aggregated in the middleware.ts for centralized control. By enforcing a single middleware file, it simplifies configuration, prevents potential conflicts, and optimizes performance by avoiding multiple middleware layers.

示例

¥Example

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

// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}

// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'

// This function can be marked `async` if using `await` inside
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}

// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}

匹配路径

¥Matching Paths

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

¥Middleware will be invoked for every route in your project. Given this, it's crucial to use matchers to precisely target or exclude specific routes. The following is the execution order:

  1. headersnext.config.js

    ¥headers from next.config.js

  2. redirectsnext.config.js

    ¥redirects from next.config.js

  3. 中间件(rewritesredirects 等)

    ¥Middleware (rewrites, redirects, etc.)

  4. beforeFiles (rewrites) 从 next.config.js

    ¥beforeFiles (rewrites) from next.config.js

  5. 文件系统路由(public/_next/static/pages/app/ 等)

    ¥Filesystem routes (public/, _next/static/, pages/, app/, etc.)

  6. afterFiles (rewrites) 从 next.config.js

    ¥afterFiles (rewrites) from next.config.js

  7. 动态路由 (/blog/[slug])

    ¥Dynamic Routes (/blog/[slug])

  8. fallback (rewrites) 从 next.config.js

    ¥fallback (rewrites) from next.config.js

有两种方法可以定义中间件将在哪些路径上运行:

¥There are two ways to define which paths Middleware will run on:

  1. 自定义匹配器配置

    ¥Custom matcher config

  2. 条件语句

    ¥Conditional statements

匹配器

¥Matcher

matcher 允许你过滤中间件以在特定路径上运行。

¥matcher allows you to filter Middleware to run on specific paths.

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

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

¥You can match a single path or multiple paths with an array syntax:

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

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

¥The matcher config allows full regex so matching like negative lookaheads or character matching is supported. An example of a negative lookahead to match all except specific paths can be seen here:

export const config = {
matcher: [
/*

* Match all request paths except for the ones starting with:

* - api (API routes)

* - _next/static (static files)

* - _next/image (image optimization files)

* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}

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

¥You can also bypass Middleware for certain requests by using the missing or has arrays, or a combination of both:

export const config = {
matcher: [
/*

* Match all request paths except for the ones starting with:

* - api (API routes)

* - _next/static (static files)

* - _next/image (image optimization files)

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

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

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

很高兴知道:matcher 值需要是常量,以便可以在构建时对其进行静态分析。诸如变量之类的动态值将被忽略。

¥Good to know: The matcher values need to be constants so they can be statically analyzed at build-time. Dynamic values such as variables will be ignored.

配置的匹配器:

¥Configured matchers:

  1. 必须以 / 开头

    ¥MUST start with /

  2. 可以包含命名参数:/about/:path 匹配 /about/a/about/b,但不匹配 /about/a/c

    ¥Can include named parameters: /about/:path matches /about/a and /about/b but not /about/a/c

  3. 可以对命名参数进行修饰符(以 : 开头):/about/:path*/about/a/b/c 匹配,因为 * 为零或更大。? 为零或一,+ 为一或多个

    ¥Can have modifiers on named parameters (starting with :): /about/:path* matches /about/a/b/c because * is zero or more. ? is zero or one and + one or more

  4. 可以使用括号括起来的正则表达式:/about/(.*)/about/:path* 相同

    ¥Can use regular expression enclosed in parenthesis: /about/(.*) is the same as /about/:path*

阅读有关 path-to-regexp 文档的更多详细信息。

¥Read more details on path-to-regexp documentation.

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

¥Good to know: For backward compatibility, Next.js always considers /public as /public/index. Therefore, a matcher of /public/:path will match.

条件语句

¥Conditional Statements

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))
}
}
import { NextResponse } from 'next/server'

export function middleware(request) {
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 允许你:

¥The NextResponse API allows you to:

  • redirect 传入请求到不同的 URL

    ¥redirect the incoming request to a different URL

  • rewrite 通过显示给定 URL 进行响应

    ¥rewrite the response by displaying a given URL

  • 设置 API 路由、getServerSidePropsrewrite 目标的请求标头

    ¥Set request headers for API Routes, getServerSideProps, and rewrite destinations

  • 设置响应 cookie

    ¥Set response cookies

  • 设置响应头

    ¥Set response headers

要从中间件生成响应,你可以:

¥To produce a response from Middleware, you can:

  1. rewrite 到产生响应的路由(页面路由处理程序

    ¥rewrite to a route (Page or Route Handler) that produces a response

  2. 直接返回 NextResponse。见 产生响应

    ¥return a NextResponse directly. See Producing a Response

使用 Cookie

¥Using Cookies

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

¥Cookies are regular headers. On a Request, they are stored in the Cookie header. On a Response they are in the Set-Cookie header. Next.js provides a convenient way to access and manipulate these cookies through the cookies extension on NextRequest and NextResponse.

  1. 对于传入的请求,cookies 带有以下方法:getgetAllsetdelete cookie。你可以检查 has 的 cookie 是否存在,或者删除 clear 的所有 cookie。

    ¥For incoming requests, cookies comes with the following methods: get, getAll, set, and delete cookies. You can check for the existence of a cookie with has or remove all cookies with clear.

  2. 对于传出响应,cookies 有以下方法 getgetAllsetdelete

    ¥For outgoing responses, cookies have the following methods get, getAll, set, and delete.

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

export function middleware(request: NextRequest) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
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

// Setting cookies on the response using the `ResponseCookies` API
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: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.

return response
}
import { NextResponse } from 'next/server'

export function middleware(request) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
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

// Setting cookies on the response using the `ResponseCookies` API
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: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header.

return response
}

设置标题

¥Setting Headers

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

¥You can set request and response headers using the NextResponse API (setting request headers is available since Next.js v13.0.0).

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

export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')

// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})

// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'

export function middleware(request) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')

// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})

// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}

很高兴知道:避免设置大标头,因为它可能会导致 431 请求标头字段太大 错误,具体取决于你的后端 Web 服务器配置。

¥Good to know: Avoid setting large headers as it might cause 431 Request Header Fields Too Large error depending on your backend web server configuration.

CORS

你可以在中间件中设置 CORS 标头以允许跨域请求,包括 simplepreflighted 请求。

¥You can set CORS headers in Middleware to allow cross-origin requests, including simple and preflighted requests.

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) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)

// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'

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

// Handle simple requests
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*',
}
import { 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) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)

// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'

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

// Handle simple requests
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 标头。

¥Good to know: You can configure CORS headers for individual routes in Route Handlers.

产生响应

¥Producing a Response

你可以通过返回 ResponseNextResponse 实例直接从中间件进行响应。(从 Next.js v13.1.0 年开始可用)

¥You can respond from Middleware directly by returning a Response or NextResponse instance. (This is available since Next.js v13.1.0)

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

// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}

export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
import { isAuthenticated } from '@lib/auth'

// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}

export function middleware(request) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}

waitUntilNextFetchEvent

¥waitUntil and NextFetchEvent

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

¥The NextFetchEvent object extends the native FetchEvent object, and includes the waitUntil() method.

waitUntil() 方法将 Promise 作为参数,并延长中间件的生命周期,直到 Promise 解决。这对于在后台执行工作很有用。

¥The waitUntil() method takes a promise as an argument, and extends the lifetime of the Middleware until the promise settles. This is useful for performing work in the background.

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

高级中间件标志

¥Advanced Middleware Flags

在 Next.js 的 v13.1 中,为中间件引入了两个附加标志 skipMiddlewareUrlNormalizeskipTrailingSlashRedirect 来处理高级用例。

¥In v13.1 of Next.js two additional flags were introduced for middleware, skipMiddlewareUrlNormalize and skipTrailingSlashRedirect to handle advanced use cases.

skipTrailingSlashRedirect 禁用 Next.js 重定向以添加或删除尾部斜杠。这允许中间件内部的自定义处理来维护某些路径的尾部斜杠,而不是其他路径,这可以使增量迁移更容易。

¥skipTrailingSlashRedirect disables Next.js redirects for adding or removing trailing slashes. This allows custom handling inside middleware to maintain the trailing slash for some paths but not others, which can make incremental migrations easier.

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

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

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

// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}

skipMiddlewareUrlNormalize 允许禁用 Next.js 中的 URL 规范化,以使直接访问和客户端转换的处理相同。在某些高级情况下,此选项通过使用原始 URL 提供完全控制。

¥skipMiddlewareUrlNormalize allows for disabling the URL normalization in Next.js to make handling direct visits and client-transitions the same. In some advanced cases, this option provides full control by using the original URL.

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

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

console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}

运行时

¥Runtime

中间件目前仅支持 边缘运行时。无法使用 Node.js 运行时。

¥Middleware currently only supports the Edge runtime. The Node.js runtime can not be used.

版本历史

¥Version History

版本变化
v13.1.0添加了高级中间件标志
v13.0.0中间件可以修改请求头、响应头、发送响应
v12.2.0中间件稳定,请参见 升级指南
v12.0.9在 Edge Runtime 中强制使用绝对 URL (PR)
v12.0.0添加了中间件(测试版)