Proxy(原 Middleware)
本教程共 42 篇 · 第 22 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
22. Proxy(原 Middleware)
本节目标:理解 Next.js 16 Proxy 的工作机制,学会使用 matcher 控制执行范围,掌握请求改写、重定向和认证拦截的常见模式。
Proxy 是什么
Next.js 16 的重要变更
Next.js 16 将
middleware文件约定重命名为proxy。原因是 “middleware” 一词容易与 Express.js 中间件混淆,导致误用。proxy更准确地描述了它的行为——在应用之前代理请求,可以运行在应用主运行时之外,在请求到达应用之前进行处理。如果你有旧的
middleware.ts文件,可以运行以下 codemod 自动迁移:npx @next/codemod@latest middleware-to-proxy .
Proxy(代理)是在请求到达路由处理之前执行的代码。它可以拦截、修改请求和响应,实现认证检查、URL 重写、A/B 测试等功能。
Proxy 默认运行在 Node.js Runtime 上,可以使用完整的 Node.js API。请注意:在 Next.js 16 中,Proxy 文件中不再支持设置 export const runtime = 'edge',否则会抛出错误。
创建 Proxy
在项目根目录(与 app 目录同级)创建 proxy.ts 文件:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// 你的代理逻辑
return NextResponse.next()
}
// 配置执行范围
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
}
与旧版 Middleware 的区别
Next.js 15 中 Middleware 默认使用 Node.js Runtime,但仍可通过
export const runtime = 'edge'切换到 Edge Runtime。Next.js 16 的 Proxy 同样默认使用 Node.js Runtime,但移除了runtime配置项,设置该选项会抛出错误。
NextResponse 的常用方法
Proxy 通过 NextResponse 对象控制请求流程:
NextResponse.next()
继续正常处理请求,可选择性修改请求头:
export function proxy(request: NextRequest) {
// 添加自定义请求头,传递给后续处理
const response = NextResponse.next({
request: {
headers: request.headers,
},
})
return response
}
NextResponse.redirect()
将用户重定向到另一个 URL:
export function proxy(request: NextRequest) {
const isLoggedIn = request.cookies.get('session')
if (!isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
NextResponse.rewrite()
重写请求路径,浏览器 URL 不变:
export function proxy(request: NextRequest) {
const locale = request.cookies.get('locale')?.value || 'zh'
// 将 /products 重写为 /zh/products 或 /en/products
return NextResponse.rewrite(
new URL(`/${locale}${request.nextUrl.pathname}`, request.url)
)
}
直接返回 Response
可以构造自定义响应直接返回,阻止请求继续:
export function proxy(request: NextRequest) {
// 限流检查
if (isRateLimited(request)) {
return new Response('请求过于频繁,请稍后再试', {
status: 429,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
})
}
return NextResponse.next()
}
matcher 配置
matcher 控制 Proxy 在哪些路径上执行。合理配置 matcher 可以避免不必要的性能开销:
export const config = {
// 匹配多个路径模式
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
}
matcher 语法
| 模式 | 说明 | 示例 |
|---|---|---|
/path | 精确匹配 | /about |
/path/:param | 单级动态段 | /blog/:slug |
/path/:path* | 多级动态段(含零级) | /docs/:path* |
/path/:param? | 可选参数 | /blog/:slug? |
排除特定路径
使用负向匹配排除不需要 Proxy 的路径:
export const config = {
// 匹配所有路径,但排除 api、静态资源和图片
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
认证拦截
最常见的 Proxy 用途是认证检查:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const protectedRoutes = ['/dashboard', '/settings', '/profile']
const publicRoutes = ['/login', '/register', '/']
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
// 检查是否是受保护路由
const isProtected = protectedRoutes.some((route) =>
pathname.startsWith(route)
)
// 检查用户是否已登录
const session = request.cookies.get('session')?.value
const isAuthenticated = Boolean(session)
// 未登录访问受保护路由 → 重定向到登录页
if (isProtected && !isAuthenticated) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', pathname)
return NextResponse.redirect(loginUrl)
}
// 已登录访问登录页 → 重定向到仪表盘
if (isAuthenticated && pathname === '/login') {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
Proxy 不是唯一的安全防线
Proxy 适合做初步的认证检查(如重定向),但真正的权限验证应该在数据访问层(Server Components、Server Actions、Route Handlers)中执行。
国际化路由
使用 Proxy 根据用户偏好自动切换语言:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const supportedLocales = ['zh', 'en', 'ja']
const defaultLocale = 'zh'
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
// 检查路径是否已包含语言前缀
const hasLocale = supportedLocales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (hasLocale) return NextResponse.next()
// 从 cookie 或 Accept-Language 头获取用户偏好
const locale =
request.cookies.get('locale')?.value ||
request.headers.get('accept-language')?.split(',')[0].split('-')[0] ||
defaultLocale
const finalLocale = supportedLocales.includes(locale) ? locale : defaultLocale
return NextResponse.rewrite(
new URL(`/${finalLocale}${pathname}`, request.url)
)
}
A/B 测试
使用 Proxy 分配用户到不同的测试组:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl
// 只在首页进行 A/B 测试
if (pathname !== '/') return NextResponse.next()
// 检查用户是否已有分组
let bucket = request.cookies.get('ab-bucket')?.value
if (!bucket) {
// 随机分配
bucket = Math.random() < 0.5 ? 'a' : 'b'
}
// 重写到对应版本
const url = request.nextUrl.clone()
url.pathname = `/${bucket}${pathname}`
const response = NextResponse.rewrite(url)
// 保存分组信息
if (!request.cookies.get('ab-bucket')) {
response.cookies.set('ab-bucket', bucket, { maxAge: 60 * 60 * 24 * 30 })
}
return response
}
地理位置服务
request.geo 已移除
Next.js 15 起,
NextRequest上的geo和ip属性已被移除。如需获取地理位置信息,请通过请求头读取。不同平台提供不同的头信息:
- Vercel:
x-vercel-ip-country、x-vercel-ip-city等- Cloudflare:
cf-ipcountry- 自托管:需配合反向代理(如 Nginx)注入地理位置头
通过请求头获取地理位置信息:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// 从请求头获取用户国家信息(不同平台头名不同)
const country = request.headers.get('x-vercel-ip-country') || 'CN'
const city = request.headers.get('x-vercel-ip-city') || 'Unknown'
// 根据地区重写内容
const url = request.nextUrl.clone()
url.searchParams.set('country', country)
return NextResponse.rewrite(url)
}
性能注意事项
Proxy 在每次匹配的请求上都会执行,因此需要注意:
- 使用 matcher 缩小范围:避免在所有请求上运行
- 保持逻辑轻量:避免复杂计算或数据库查询
- 避免阻塞操作:Proxy 中的异步操作会延迟响应
- 注意 Cookie 读取:
cookies()在 Proxy 中同步读取,但在页面中会触发动态渲染
小结
- 创建位置:项目根目录的
proxy.ts(Next.js 16 从middleware.ts重命名而来) - 核心方法:
NextResponse.next()、redirect()、rewrite() - matcher:精确控制 Proxy 的执行范围
- 常见用途:认证拦截、国际化、A/B 测试
- 安全原则:Proxy 做初步检查,真正的权限验证在数据层
- Next.js 16:Proxy 默认使用 Node.js Runtime,不支持切换到 Edge Runtime;
request.geo和request.ip已移除