Next.js 中间件与边缘函数从请求拦截到全球加速的深度实践一、服务端逻辑的最后一公里为什么需要在边缘执行Web 应用的请求处理链路中存在大量轻量但高频的操作——身份验证、A/B 测试分流、地域重定向、Bot 检测。这些操作的传统做法是在 Node.js 服务端或 API 路由中处理但每次请求都要穿越到单一区域的源站延迟动辄 100-300ms。对于全球用户而言这种所有请求都回源的架构在延迟和可用性上都是不可接受的。Next.js 中间件Middleware和边缘函数Edge Functions提供了一种在 CDN 边缘节点执行逻辑的方案。代码部署在全球 300 个 PoP 节点上用户请求在最近的边缘节点即可完成处理无需回源。这种架构将认证、重定向等逻辑的延迟从数百毫秒降低到个位数毫秒。二、中间件执行模型与边缘运行时原理Next.js 中间件的核心执行模型是请求拦截 → 条件匹配 → 响应改写。中间件在 Edge Runtime 中运行这是一个基于 V8 的轻量运行时不支持 Node.js 的全部 API如 fs、crypto 的某些方法但支持 Web 标准 APIFetch、Request、Response。flowchart TD A[用户请求] -- B{CDN 边缘节点} B -- C[Middleware 执行] C -- D{匹配规则判断} D --|认证失败| E[返回 401 / 重定向登录] D --|地域分流| F[重写 URL 到对应语言版本] D --|Bot 检测| G[返回验证页面 / Block] D --|正常请求| H[继续到源站或缓存] H -- I[返回响应] E -- J[用户收到响应] F -- J G -- J I -- J关键设计约束执行时间限制边缘函数的执行时间通常限制在 50ms 以内Vercel 平台复杂逻辑必须拆分包体积限制中间件打包后的体积不能超过 1MB含依赖需要精简依赖选择无状态执行边缘节点间不共享内存状态缓存需依赖 KV 存储或 Cache API三、生产级中间件实现与最佳实践// middleware.ts — Next.js 边缘中间件 // 设计意图在 CDN 边缘节点执行认证、分流和 Bot 检测 // 避免请求回源将延迟控制在 10ms 以内 import { NextResponse } from next/server; import type { NextRequest } from next/server; // 轻量级 JWT 验证不使用 jsonwebtoken因其依赖 Node.js crypto // 使用 Web Crypto API 实现兼容 Edge Runtime async function verifyToken(token: string, secret: string): Promiseboolean { try { const parts token.split(.); if (parts.length ! 3) return false; // 使用 Web Crypto API 验证签名 const encoder new TextEncoder(); const key await crypto.subtle.importKey( raw, encoder.encode(secret), { name: HMAC, hash: SHA-256 }, false, [verify] ); const signature Uint8Array.from( atob(parts[2].replace(/-/g, ).replace(/_/g, /)), c c.charCodeAt(0) ); const data encoder.encode(${parts[0]}.${parts[1]}); const valid await crypto.subtle.verify(HMAC, key, signature, data); return valid; } catch { return false; } } // Bot 检测基于 User-Agent 和请求头特征 function isBot(request: NextRequest): boolean { const ua request.headers.get(user-agent) || ; const botPatterns [ /bot/i, /crawler/i, /spider/i, /headless/i, /python-requests/i, /curl/i, /wget/i, ]; if (botPatterns.some(p p.test(ua))) return true; // Headless Chrome 检测缺少 WebDriver 标志但行为异常 const secChUa request.headers.get(sec-ch-ua) || ; if (secChUa.includes(HeadlessChrome)) return true; return false; } // 地域分流根据请求 IP 所属国家重定向 function getLocaleRedirect(request: NextRequest): string | null { const country request.geo?.country || US; const localeMap: Recordstring, string { CN: /zh, JP: /ja, KR: /ko, DE: /de, FR: /fr, }; return localeMap[country] || null; } export async function middleware(request: NextRequest) { const { pathname } request.nextUrl; // 规则 1静态资源和 API 路由跳过中间件 if ( pathname.startsWith(/_next) || pathname.startsWith(/api) || pathname.includes(.) ) { return NextResponse.next(); } // 规则 2Bot 检测与限流 if (isBot(request)) { // 对爬虫返回轻量验证页面而非完整渲染 const verifyUrl request.nextUrl.clone(); verifyUrl.pathname /verify; return NextResponse.rewrite(verifyUrl); } // 规则 3认证检查仅保护 /dashboard 路径 if (pathname.startsWith(/dashboard)) { const token request.cookies.get(auth_token)?.value; if (!token) { const loginUrl request.nextUrl.clone(); loginUrl.pathname /login; loginUrl.searchParams.set(callbackUrl, pathname); return NextResponse.redirect(loginUrl); } // 在边缘节点验证 JWT避免回源到认证服务 const isValid await verifyToken(token, process.env.JWT_SECRET || ); if (!isValid) { const loginUrl request.nextUrl.clone(); loginUrl.pathname /login; // 清除无效 token const response NextResponse.redirect(loginUrl); response.cookies.delete(auth_token); return response; } } // 规则 4地域分流仅对首页生效 if (pathname /) { const localePath getLocaleRedirect(request); if (localePath) { const redirectUrl request.nextUrl.clone(); redirectUrl.pathname localePath; // 使用 rewrite 而非 redirectURL 不变但内容切换 return NextResponse.rewrite(redirectUrl); } } // 规则 5A/B 测试分流 if (pathname.startsWith(/pricing)) { const bucket request.cookies.get(ab_bucket)?.value; if (!bucket) { // 基于用户 ID 哈希分桶确保同一用户始终看到同一版本 const userId request.cookies.get(user_id)?.value || crypto.randomUUID(); const hash userId.split().reduce((acc, c) acc c.charCodeAt(0), 0); const assignedBucket hash % 2 0 ? A : B; const response NextResponse.next(); response.cookies.set(ab_bucket, assignedBucket, { maxAge: 60 * 60 * 24 * 30, // 30 天 path: /, sameSite: lax, }); return response; } // B 版本用户看到不同的定价页面 if (bucket B) { const rewriteUrl request.nextUrl.clone(); rewriteUrl.pathname /pricing-v2; return NextResponse.rewrite(rewriteUrl); } } return NextResponse.next(); } // 精确配置匹配路径避免不必要的中间件执行 export const config { matcher: [ /((?!_next/static|_next/image|favicon.ico).*), ], };四、边缘架构的 Trade-offs 与适用边界冷启动延迟边缘函数的首次调用存在冷启动问题通常在 50-200ms 之间。虽然比传统 Serverless 冷启动快V8 隔离而非容器但对于要求 P99 10ms 的场景仍需关注。解决方案是保持函数活跃定时 ping或使用 Vercel 的 Edge Function 预热机制。运行时限制Edge Runtime 不支持 Node.js 的完整 API。无法使用fs、child_process、net等模块也无法使用依赖这些模块的第三方库如jsonwebtoken、mongoose。在选型时必须确认所有依赖兼容 Web 标准 API否则运行时会抛出异常。调试与可观测性边缘函数的调试比 Node.js 服务端困难得多。日志分散在 300 个 PoP 节点上传统的集中式日志方案不适用。需要依赖平台提供的 Edge Logging如 Vercel Edge Logs或自建日志采集管线将边缘日志汇总到中心化存储。成本考量边缘函数按调用次数和执行时间计费高频轻量请求如认证检查的成本通常低于传统 Serverless但涉及大量计算的场景如复杂的数据转换可能比在单一区域运行更贵因为每个边缘节点都要执行一次。五、总结Next.js 中间件和边缘函数将 Web 应用的轻量级请求处理从源站下沉到 CDN 边缘显著降低了认证、分流、Bot 检测等操作的延迟。核心价值在于就近处理——用户请求在最近的边缘节点完成逻辑判断无需回源。但边缘运行时的 API 限制、冷启动延迟和调试困难是需要权衡的因素。在实际落地中建议将中间件严格限定在轻量决策场景认证、重定向、分流将复杂业务逻辑保留在 Node.js API 路由或独立微服务中。随着 Edge Runtime 生态的成熟和 Web 标准 API 的完善边缘函数的适用场景将持续扩展成为现代 Web 架构的标准组件。
Next.js 中间件与边缘函数:从请求拦截到全球加速的深度实践
Next.js 中间件与边缘函数从请求拦截到全球加速的深度实践一、服务端逻辑的最后一公里为什么需要在边缘执行Web 应用的请求处理链路中存在大量轻量但高频的操作——身份验证、A/B 测试分流、地域重定向、Bot 检测。这些操作的传统做法是在 Node.js 服务端或 API 路由中处理但每次请求都要穿越到单一区域的源站延迟动辄 100-300ms。对于全球用户而言这种所有请求都回源的架构在延迟和可用性上都是不可接受的。Next.js 中间件Middleware和边缘函数Edge Functions提供了一种在 CDN 边缘节点执行逻辑的方案。代码部署在全球 300 个 PoP 节点上用户请求在最近的边缘节点即可完成处理无需回源。这种架构将认证、重定向等逻辑的延迟从数百毫秒降低到个位数毫秒。二、中间件执行模型与边缘运行时原理Next.js 中间件的核心执行模型是请求拦截 → 条件匹配 → 响应改写。中间件在 Edge Runtime 中运行这是一个基于 V8 的轻量运行时不支持 Node.js 的全部 API如 fs、crypto 的某些方法但支持 Web 标准 APIFetch、Request、Response。flowchart TD A[用户请求] -- B{CDN 边缘节点} B -- C[Middleware 执行] C -- D{匹配规则判断} D --|认证失败| E[返回 401 / 重定向登录] D --|地域分流| F[重写 URL 到对应语言版本] D --|Bot 检测| G[返回验证页面 / Block] D --|正常请求| H[继续到源站或缓存] H -- I[返回响应] E -- J[用户收到响应] F -- J G -- J I -- J关键设计约束执行时间限制边缘函数的执行时间通常限制在 50ms 以内Vercel 平台复杂逻辑必须拆分包体积限制中间件打包后的体积不能超过 1MB含依赖需要精简依赖选择无状态执行边缘节点间不共享内存状态缓存需依赖 KV 存储或 Cache API三、生产级中间件实现与最佳实践// middleware.ts — Next.js 边缘中间件 // 设计意图在 CDN 边缘节点执行认证、分流和 Bot 检测 // 避免请求回源将延迟控制在 10ms 以内 import { NextResponse } from next/server; import type { NextRequest } from next/server; // 轻量级 JWT 验证不使用 jsonwebtoken因其依赖 Node.js crypto // 使用 Web Crypto API 实现兼容 Edge Runtime async function verifyToken(token: string, secret: string): Promiseboolean { try { const parts token.split(.); if (parts.length ! 3) return false; // 使用 Web Crypto API 验证签名 const encoder new TextEncoder(); const key await crypto.subtle.importKey( raw, encoder.encode(secret), { name: HMAC, hash: SHA-256 }, false, [verify] ); const signature Uint8Array.from( atob(parts[2].replace(/-/g, ).replace(/_/g, /)), c c.charCodeAt(0) ); const data encoder.encode(${parts[0]}.${parts[1]}); const valid await crypto.subtle.verify(HMAC, key, signature, data); return valid; } catch { return false; } } // Bot 检测基于 User-Agent 和请求头特征 function isBot(request: NextRequest): boolean { const ua request.headers.get(user-agent) || ; const botPatterns [ /bot/i, /crawler/i, /spider/i, /headless/i, /python-requests/i, /curl/i, /wget/i, ]; if (botPatterns.some(p p.test(ua))) return true; // Headless Chrome 检测缺少 WebDriver 标志但行为异常 const secChUa request.headers.get(sec-ch-ua) || ; if (secChUa.includes(HeadlessChrome)) return true; return false; } // 地域分流根据请求 IP 所属国家重定向 function getLocaleRedirect(request: NextRequest): string | null { const country request.geo?.country || US; const localeMap: Recordstring, string { CN: /zh, JP: /ja, KR: /ko, DE: /de, FR: /fr, }; return localeMap[country] || null; } export async function middleware(request: NextRequest) { const { pathname } request.nextUrl; // 规则 1静态资源和 API 路由跳过中间件 if ( pathname.startsWith(/_next) || pathname.startsWith(/api) || pathname.includes(.) ) { return NextResponse.next(); } // 规则 2Bot 检测与限流 if (isBot(request)) { // 对爬虫返回轻量验证页面而非完整渲染 const verifyUrl request.nextUrl.clone(); verifyUrl.pathname /verify; return NextResponse.rewrite(verifyUrl); } // 规则 3认证检查仅保护 /dashboard 路径 if (pathname.startsWith(/dashboard)) { const token request.cookies.get(auth_token)?.value; if (!token) { const loginUrl request.nextUrl.clone(); loginUrl.pathname /login; loginUrl.searchParams.set(callbackUrl, pathname); return NextResponse.redirect(loginUrl); } // 在边缘节点验证 JWT避免回源到认证服务 const isValid await verifyToken(token, process.env.JWT_SECRET || ); if (!isValid) { const loginUrl request.nextUrl.clone(); loginUrl.pathname /login; // 清除无效 token const response NextResponse.redirect(loginUrl); response.cookies.delete(auth_token); return response; } } // 规则 4地域分流仅对首页生效 if (pathname /) { const localePath getLocaleRedirect(request); if (localePath) { const redirectUrl request.nextUrl.clone(); redirectUrl.pathname localePath; // 使用 rewrite 而非 redirectURL 不变但内容切换 return NextResponse.rewrite(redirectUrl); } } // 规则 5A/B 测试分流 if (pathname.startsWith(/pricing)) { const bucket request.cookies.get(ab_bucket)?.value; if (!bucket) { // 基于用户 ID 哈希分桶确保同一用户始终看到同一版本 const userId request.cookies.get(user_id)?.value || crypto.randomUUID(); const hash userId.split().reduce((acc, c) acc c.charCodeAt(0), 0); const assignedBucket hash % 2 0 ? A : B; const response NextResponse.next(); response.cookies.set(ab_bucket, assignedBucket, { maxAge: 60 * 60 * 24 * 30, // 30 天 path: /, sameSite: lax, }); return response; } // B 版本用户看到不同的定价页面 if (bucket B) { const rewriteUrl request.nextUrl.clone(); rewriteUrl.pathname /pricing-v2; return NextResponse.rewrite(rewriteUrl); } } return NextResponse.next(); } // 精确配置匹配路径避免不必要的中间件执行 export const config { matcher: [ /((?!_next/static|_next/image|favicon.ico).*), ], };四、边缘架构的 Trade-offs 与适用边界冷启动延迟边缘函数的首次调用存在冷启动问题通常在 50-200ms 之间。虽然比传统 Serverless 冷启动快V8 隔离而非容器但对于要求 P99 10ms 的场景仍需关注。解决方案是保持函数活跃定时 ping或使用 Vercel 的 Edge Function 预热机制。运行时限制Edge Runtime 不支持 Node.js 的完整 API。无法使用fs、child_process、net等模块也无法使用依赖这些模块的第三方库如jsonwebtoken、mongoose。在选型时必须确认所有依赖兼容 Web 标准 API否则运行时会抛出异常。调试与可观测性边缘函数的调试比 Node.js 服务端困难得多。日志分散在 300 个 PoP 节点上传统的集中式日志方案不适用。需要依赖平台提供的 Edge Logging如 Vercel Edge Logs或自建日志采集管线将边缘日志汇总到中心化存储。成本考量边缘函数按调用次数和执行时间计费高频轻量请求如认证检查的成本通常低于传统 Serverless但涉及大量计算的场景如复杂的数据转换可能比在单一区域运行更贵因为每个边缘节点都要执行一次。五、总结Next.js 中间件和边缘函数将 Web 应用的轻量级请求处理从源站下沉到 CDN 边缘显著降低了认证、分流、Bot 检测等操作的延迟。核心价值在于就近处理——用户请求在最近的边缘节点完成逻辑判断无需回源。但边缘运行时的 API 限制、冷启动延迟和调试困难是需要权衡的因素。在实际落地中建议将中间件严格限定在轻量决策场景认证、重定向、分流将复杂业务逻辑保留在 Node.js API 路由或独立微服务中。随着 Edge Runtime 生态的成熟和 Web 标准 API 的完善边缘函数的适用场景将持续扩展成为现代 Web 架构的标准组件。