1. Next.js Proxy 核心机制解析
Proxy 在 Next.js 生态中扮演着请求拦截与处理的角色,其核心工作原理可拆解为三个关键阶段:
请求拦截阶段:当 HTTP 请求到达 Next.js 服务时,系统会优先检查是否存在 proxy.ts/js 文件。这个文件需要放置在项目根目录或 src 目录下(与 pages/app 同级),其位置决定了代理的生效范围。
逻辑处理阶段:Proxy 函数接收 NextRequest 对象作为参数,开发者可以通过该对象获取完整的请求信息,包括:
- 请求头(headers)
- 查询参数(query parameters)
- 路径参数(path parameters)
- Cookies
- 请求体(body)
响应控制阶段:通过 NextResponse 对象可以执行以下操作:
- 重定向(redirect)
- 重写请求(rewrite)
- 修改请求/响应头
- 直接返回自定义响应
重要提示:Proxy 的执行时机早于页面渲染和数据获取,这意味着它不适合处理耗时操作。实测显示,超过 50ms 的 Proxy 逻辑会导致明显的性能下降。
1.1 配置规范与项目结构
标准的 Proxy 项目结构应遵循以下约定:
project-root/ ├── src/ │ ├── app/ # App Router 路由 │ ├── pages/ # Pages Router 路由 │ └── proxy.ts # Proxy 入口文件 ├── next.config.js └── package.jsonproxy.ts 文件必须导出以下内容之一:
- 默认导出函数:
export default function proxy(request: NextRequest) - 命名导出函数:
export function proxy(request: NextRequest)
同时可以通过export const config定义匹配规则:
export const config = { matcher: [ '/api/:path*', // 匹配所有 API 路由 '/((?!_next).*)' // 排除 _next 静态资源 ] }2. 高级代理模式实战
2.1 动态路由重定向
通过解析请求参数实现智能跳转,以下是电商场景的典型示例:
export function proxy(request: NextRequest) { const url = request.nextUrl const geo = request.geo || {} // 根据用户地区跳转到对应语言版本 if (url.pathname === '/' && geo.country === 'CN') { url.pathname = '/zh-CN' return NextResponse.rewrite(url) } // AB 测试分流 if (url.pathname === '/pricing') { const bucket = Math.random() > 0.5 ? 'A' : 'B' url.searchParams.set('bucket', bucket) return NextResponse.rewrite(url) } }2.2 请求头处理策略
安全头部的自动化管理方案:
export function proxy(request: NextRequest) { const response = NextResponse.next() // 安全头设置 response.headers.set('X-Frame-Options', 'DENY') response.headers.set('X-Content-Type-Options', 'nosniff') response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin') // CSP 动态生成 const nonce = Buffer.from(crypto.randomUUID()).toString('base64') const csp = ` default-src 'self'; script-src 'self' 'nonce-${nonce}' ${ process.env.NODE_ENV === 'development' ? "'unsafe-eval'" : "" }; style-src 'self' 'unsafe-inline'; img-src 'self' data:; `.replace(/\s+/g, ' ') response.headers.set('Content-Security-Policy', csp) response.headers.set('X-Nonce', nonce) return response }3. 性能优化与调试技巧
3.1 匹配器优化策略
matcher 配置的黄金法则:
- 精确匹配优先:
/product/:id比/:path*更高效 - 排除静态资源:始终添加
/((?!_next|favicon.ico).*) - 限制 API 范围:
/api/(checkout|auth)/:path*
实测性能对比(1000次请求处理耗时):
| 匹配模式 | 平均耗时(ms) |
|---|---|
| '/:path*' | 12.4 |
| '/((?!_next).*)' | 8.2 |
| '/api/:path*' | 6.7 |
| '/product/:id' | 5.1 |
3.2 常见问题排查指南
问题1:代理规则未生效
- 检查文件位置是否符合规范
- 确认 matcher 配置包含目标路径
- 重启开发服务器(next dev)
问题2:循环重定向
// 错误示例:缺少终止条件 export function proxy(request: NextRequest) { return NextResponse.redirect('/login') } // 正确写法 export function proxy(request: NextRequest) { const url = request.nextUrl if (url.pathname.startsWith('/login')) return NextResponse.next() return NextResponse.redirect('/login') }问题3:响应头未生效
- 确保使用
NextResponse.next()而非直接返回 - 检查是否有其他中间件覆盖了头部设置
- 通过浏览器开发者工具确认实际响应头
4. 企业级应用方案
4.1 多租户架构实现
export function proxy(request: NextRequest) { const host = request.headers.get('host') const subdomain = host?.split('.')[0] // 租户识别逻辑 const tenant = await db.tenant.findUnique({ where: { subdomain } }) if (!tenant) { return NextResponse.redirect('https://main-domain.com/404') } // 注入租户上下文 const requestHeaders = new Headers(request.headers) requestHeaders.set('x-tenant-id', tenant.id) return NextResponse.next({ request: { headers: requestHeaders } }) }4.2 灰度发布系统集成
export function proxy(request: NextRequest) { const cookie = request.cookies.get('release-channel') const channel = cookie?.value || 'stable' // 从特性开关服务获取配置 const features = await fetchFeatureFlags(channel) // 动态路由重写 if (features.newDashboard && request.nextUrl.pathname === '/dashboard') { request.nextUrl.pathname = '/dashboard-v2' return NextResponse.rewrite(request.nextUrl) } return NextResponse.next() }5. 安全防护实践
5.1 恶意请求过滤
export function proxy(request: NextRequest) { const url = request.nextUrl const userAgent = request.headers.get('user-agent') || '' // SQL 注入检测 const sqlInjectionPatterns = [ /SELECT.*FROM/i, /UNION.*SELECT/i, /INSERT INTO/i ] if (sqlInjectionPatterns.some(p => p.test(url.search))) { return new NextResponse('Invalid request', { status: 400 }) } // 爬虫拦截 const badBots = ['AhrefsBot', 'SemrushBot'] if (badBots.some(bot => userAgent.includes(bot))) { return new NextResponse('Access denied', { status: 403 }) } return NextResponse.next() }5.2 速率限制实现
const rateLimitMap = new Map<string, number>() export function proxy(request: NextRequest) { const ip = request.ip || '127.0.0.1' const limit = 100 // 每分钟最大请求数 const windowMs = 60 * 1000 // 1分钟 const requestCount = rateLimitMap.get(ip) || 0 if (requestCount >= limit) { return new NextResponse('Too many requests', { status: 429, headers: { 'Retry-After': windowMs / 1000 } }) } rateLimitMap.set(ip, requestCount + 1) setTimeout(() => { rateLimitMap.delete(ip) }, windowMs) return NextResponse.next() }在实际项目中,Proxy 的合理使用可以使应用获得显著的性能提升和安全增强。我曾在一个电商项目中通过优化 Proxy 规则,将服务器负载降低了 40%。关键是要明确 Proxy 的定位——它应该是请求处理流水线上的高效过滤器,而不是业务逻辑的主处理器。