外观
中间件
中间件
中间件允许你在请求完成之前运行代码。然后,基于传入的请求,你可以通过重写、重定向、修改请求或响应头,或直接响应来修改响应。
中间件在缓存内容和路由匹配之前运行。有关更多详细信息,请参阅匹配路径。
使用场景
将中间件集成到你的应用程序中可以显著提高性能、安全性和用户体验。中间件特别有效的一些常见场景包括:
- 身份验证和授权:在授予对特定页面或 API 路由的访问权限之前,确保用户身份并检查会话 cookie。
- 服务器端重定向:基于某些条件(例如,区域设置、用户角色)在服务器级别重定向用户。
- 路径重写:通过根据请求属性动态重写到 API 路由或页面的路径来支持 A/B 测试、功能推出或遗留路径。
- 机器人检测:通过检测和阻止机器人流量来保护你的资源。
- 日志记录和分析:在页面或 API 处理之前捕获和分析请求数据以获取洞察。
- 功能标记:动态启用或禁用功能,以实现无缝功能推出或测试。
认识到中间件可能不是最佳方法的情况同样重要。以下是一些需要注意的场景:
- 复杂的数据获取和操作:中间件不是为直接数据获取或操作而设计的,这应该在路由处理程序或服务器端实用程序中完成。
- 繁重的计算任务:中间件应该是轻量级的并快速响应,否则可能会导致页面加载延迟。繁重的计算任务或长时间运行的进程应该在专用的路由处理程序中完成。
- 广泛的会话管理:虽然中间件可以管理基本的会话任务,但广泛的会话管理应由专用的身份验证服务或在路由处理程序中管理。
- 直接数据库操作:不建议在中间件中执行直接数据库操作。数据库交互应在路由处理程序或服务器端实用程序中完成。
约定
使用项目根目录中的 middleware.ts
(或 .js
) 文件来定义中间件。例如,与 pages
或 app
在同一级别,或者如果适用的话在 src
内。
使用项目根目录中的 middleware.ts
(或 .js
) 文件来定义中间件。例如,与 pages
或 app
在同一级别,或者如果适用的话在 src
内。
提示
虽然每个项目只支持一个 middleware.ts
文件,但你仍然可以模块化地组织你的中间件逻辑。将中间件功能分解到单独的 .ts
或 .js
文件中,并将它们导入到主 middleware.ts
文件中。这允许更清晰地管理特定路由的中间件,并在 middleware.ts
中集中控制。通过强制使用单个中间件文件,它简化了配置,防止了潜在的冲突,并通过避免多个中间件层优化了性能。
示例
middleware.ts
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*',
}
middleware.js
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*',
}
匹配路径
中间件将被调用用于你项目中的每个路由。鉴于此,使用匹配器精确地定位或排除特定路由至关重要。以下是执行顺序:
next.config.js
中的headers
next.config.js
中的redirects
- 中间件 (
rewrites
,redirects
等) next.config.js
中的beforeFiles
(rewrites
)- 文件系统路由 (
public/
,_next/static/
,pages/
,app/
等) next.config.js
中的afterFiles
(rewrites
)- 动态路由 (
/blog/[slug]
) next.config.js
中的fallback
(rewrites
)
有两种方法可以定义中间件将在哪些路径上运行:
匹配器
matcher
允许你过滤中间件以在特定路径上运行。
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: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
你也可以使用 missing
或 has
数组,或两者的组合,为某些请求绕过中间件:
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
值需要是常量,以便它们可以在构建时进行静态分析。动态值(如变量)将被忽略。
配置的匹配器:
- 必须以
/
开头 - 可以包含命名参数:
/about/:path
匹配/about/a
和/about/b
但不匹配/about/a/c
- 可以在命名参数上有修饰符(以
:
开头):/about/:path*
匹配/about/a/b/c
,因为*
是 零个或多个。?
是 零个或一个,+
是 一个或多个 - 可以使用括号中的正则表达式:
/about/(.*)
与/about/:path*
相同
在 path-to-regexp 文档中阅读更多详细信息。
提示
为了向后兼容,Next.js 总是将 /public
视为 /public/index
。因此,/public/:path
的匹配器将匹配。
条件语句
middleware.ts
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))
}
}
middleware.js
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 允许你:
redirect
传入请求到不同的 URLrewrite
响应,显示给定的 URL- 为 API 路由、
getServerSideProps
和rewrite
目标设置请求头 - 设置响应 cookie
- 设置响应头
要从中间件生成响应,你可以:
使用 Cookies
Cookies 是常规头。在 Request
上,它们存储在 Cookie
头中。在 Response
上,它们在 Set-Cookie
头中。Next.js 通过 NextRequest
和 NextResponse
上的 cookies
扩展提供了一种方便的方式来访问和操作这些 cookies。
- 对于传入的请求,
cookies
带有以下方法:get
、getAll
、set
和delete
cookies。你可以使用has
检查 cookie 是否存在,或使用clear
删除所有 cookies。 - 对于传出的响应,
cookies
有以下方法:get
、getAll
、set
和delete
。
middleware.ts
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
}
middleware.js
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
}
设置头
你可以使用 NextResponse
API 设置请求和响应头(设置 请求 头从 Next.js v13.0.0 开始可用)。
middleware.ts
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.next
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
}
middleware.js
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.next
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 服务器配置。
CORS
你可以在中间件中设置 CORS 头以允许跨域请求,包括简单和预检请求。
middleware.ts
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*',
}
middleware.js
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 头。
生成响应
你可以直接从中间件返回一个 Response
或 NextResponse
实例来响应(从 Next.js v13.1.0 开始可用)。
middleware.ts
import type { 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 }
)
}
}
middleware.js
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 }
)
}
}
waitUntil
和 NextFetchEvent
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()
}
高级中间件标志
在 v13.1
版本的 Next.js 中,引入了两个额外的中间件标志,skipMiddlewareUrlNormalize
和 skipTrailingSlashRedirect
,用于处理高级用例。
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()
}
// apply trailing slash handling
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)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
运行时
中间件当前仅支持边缘运行时。无法使用 Node.js 运行时。
版本历史
版本 | 变更 |
---|---|
v13.1.0 | 添加了高级中间件标志 |
v13.0.0 | 中间件可以修改请求头、响应头和发送响应 |
v12.2.0 | 中间件已稳定,请参阅升级指南 |
v12.0.9 | 在边缘运行时中强制使用绝对 URL (PR) |
v12.0.0 | 中间件 (Beta) 已添加 |