代理(Proxy)是 Next.js 中一个强大的功能,它允许我们在请求到达页面或 API 路由之前拦截和处理请求。这对于实现身份验证、日志记录、重定向、请求修改等功能非常有用。
在 Next.js 16 中,原先的 middleware.ts 已被重命名为 proxy.ts,以更清晰地表示其在应用程序网络边界的作用。在这一节课,我们将学习如何使用代理,了解代理的执行时机,以及如何在实际项目中应用代理来解决各种问题。

代理在 Next.js 中是一个特殊的函数,它会在每个请求到达路由处理器之前执行。这意味着代理可以访问请求对象,修改请求或响应,或者完全阻止请求继续处理。
代理必须放在项目根目录下的 proxy.ts 或 proxy.js 文件中。这个文件会被 Next.js 自动识别并执行。需要注意的是,proxy 运行在 Node.js 运行时,不再支持 Edge 运行时。如果你需要 Edge 运行时的功能,可以继续使用已弃用的 middleware.ts,但建议迁移到新的 proxy.ts。
让我们从一个最简单的例子开始:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { console.log('请求路径:', request.nextUrl.pathname); return NextResponse.next(); } export const config = { matcher: '/about/:path*', };
这个例子展示了代理的基本结构。我们导出了一个 proxy 函数,它接收一个 NextRequest 对象作为参数。函数内部,我们记录了请求路径,然后调用 NextResponse.next() 让请求继续处理。
config 对象中的 matcher 属性定义了代理应该匹配哪些路径。在这个例子中,代理只会对 /about 及其子路径执行。
代理不仅可以拦截请求,还可以对请求和响应进行各种修改,这让我们能够在请求正式到达 API 路由或页面之前进行精细的定制。 例如,我们可以为请求动态添加、修改或移除请求头,也可以注入自定义响应头、改变响应体等。下面我们先看一下如何在代理中修改请求头的具体做法:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { // 克隆请求头 const requestHeaders = new Headers(request.headers); // 添加自定义头 requestHeaders.set('x-custom-header', 'my-value'); // 返回修改后的请求
在这个例子中,我们克隆了请求头,添加了一个自定义头,然后返回修改后的请求。这样,目标路由就可以访问这个自定义头了。
我们也可以修改响应:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { const response = NextResponse.next(); // 添加响应头 response.headers.set('x-custom-header', 'my-value'); return response;
这个例子在响应中添加了一个自定义头。客户端可以读取这个头来获取额外的信息。
代理不仅可以转发请求,还可以灵活地实现“重定向”和“重写”两种功能。
重定向(redirect)会直接把客户端的 URL 跳转到一个新的地址,用户在地址栏就能看到新的 URL。例如,如果访问 /old-path 被重定向到 /new-path,用户看到的 URL 会从 /old-path 变为 /new-path。
重写(rewrite)则是在不改变用户地址栏里所见 URL 的前提下,将请求内容转发到实际的另一个路径。比如把对 /blog 的请求内容“悄悄”映射到 /posts,但用户地址栏始终显示 /blog。
这种能力适用于实现 URL 美化、多语言路由、A/B 测试等多种需求,是中间件代理的核心用法之一。
让我们看一个重定向的例子:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { // 如果访问 /old-path,重定向到 /new-path if (request.nextUrl.pathname === '/old-path') { return NextResponse.redirect(new URL('/new-path', request.url)); } return
这个例子检查请求路径,如果是 /old-path,就重定向到 /new-path。注意,我们使用 new URL() 来创建绝对 URL,这是重定向所必需的。
重写的例子:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { // 如果访问 /blog,重写到 /posts(URL 不变) if (request.nextUrl.pathname === '/blog') { return NextResponse.rewrite(new URL('/posts', request.url)); } return
这个例子将 /blog 的请求重写到 /posts,但 URL 保持不变。这对于 A/B 测试、多语言支持等场景很有用。
代理的一个最常见且重要的应用场景就是实现身份验证保护。比如,在实际项目中,我们通常希望只有登录用户才能访问某些受保护的页面或接口。 这时,可以通过代理(中间件)的方式,在所有请求到达后端处理之前,提前检查用户是否已经登录。如果未登录,则可以把用户自动重定向到登录页:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function proxy(request: NextRequest) { // 获取 token const token = request.cookies.get('auth-token')?.value; // 如果访问受保护的路径且没有 token,重定向到登录页 if (request.nextUrl.pathname.startsWith('/dashboard')
这个代理做了几件事。首先,它从 cookie 中获取认证 token。然后,它检查请求是否访问受保护的路径。如果是且没有 token,就重定向到登录页,并在查询参数中保存原始路径,这样登录后可以返回。 如果有 token,代理会验证它。如果 token 无效,代理会清除 cookie 并重定向到登录页。
config.matcher 定义了代理应该匹配的路径。在这个例子中,代理只对 /dashboard 和 /api/protected 路径执行。
代理(中间件)不仅可以做身份验证保护,还可以实现多语言支持。具体来说,在用户访问页面时,我们可以自动检测用户的浏览器首选语言,并根据这一信息决定应该为其展示哪种语言的内容。
这样,用户首次访问网站时,就会被自动重定向到对应语言的站点路径。例如,如果用户浏览器设置的是英文,首次访问 / 时会被重定向到 /en,如果是中文则会跳转到 /zh。下面是一个实现多语言自动切换的完整示例:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; const locales = ['zh', 'en']; const defaultLocale = 'zh'; function getLocale(request: NextRequest) { // 从 Accept-Language 头获取首选语言 const acceptLanguage = request.headers.get(
这个代理实现了自动语言检测和重定向。它检查请求路径是否已经包含语言代码,如果没有,就从 Accept-Language 头中检测用户的首选语言,然后重定向到带语言代码的路径。
config.matcher 使用了负向前瞻正则表达式,排除了 API 路由、Next.js 内部文件等路径。
代理还可以用来记录详细的请求日志。可以在代理函数中收集每个请求的关键信息,比如 HTTP 方法、请求路径、用户的 IP 地址、User-Agent 头,以及请求的时间戳等。
这样做有助于后续排查问题、监控流量、分析用户行为。例如,可以在接收请求时将这些信息通过 console.log 输出到终端,或者发送到日志收集平台。
同时,还可以计算每个请求的处理耗时,将其记录在响应头中,通过浏览器或监控工具查看每次请求的响应性能。
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { const start = Date.now(); // 记录请求信息 console.log({ method: request.method, path: request.nextUrl.pathname, ip: request.ip, userAgent: request.headers.get(
这个代理记录了每个请求的方法、路径、IP 地址和用户代理,并在响应头中添加了处理时间。这对于监控和调试很有用。
代理可以用于实现速率限制(Rate Limiting),用于限制每个客户端在一定时间窗口内的请求次数,以防止 API 被滥用或遭受恶意攻击。 例如,可以根据用户的 IP 地址,在一分钟内只允许其最多请求 100 次 API。如果超过限制,服务器就会返回 429 Too Many Requests 错误,从而保护后端资源和服务稳定性。
速率限制常见的实现方式有内存存储、分布式缓存(如 Redis)、或第三方服务。在下方的示例中,我们通过一个简单的内存 Map 构建速率计数器:每当有 API 请求进来时,记录请求源 IP 的请求次数和窗口重置时间,每次请求先检查是否超限,若超限则立即终止请求并返回错误响应。 此机制可有效降低突发流量和刷接口风险,对于小型应用或原型开发已足够,如需在集群和高并发环境下使用,建议将计数数据放入如 Redis 这类集中式缓存系统,以便统计全局状态。
下方代码即实现了按 IP 计数的速率限制逻辑。
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; // 简单的内存存储(生产环境应使用 Redis 等) const rateLimitMap = new Map<string, { count: number; resetTime: number }>(); function rateLimit(ip: string, limit: number,
这个代理实现了简单的速率限制。它使用内存存储来跟踪每个 IP 的请求次数,如果超过限制,就返回 429 状态码。注意,这个例子使用内存存储,只适用于单服务器部署。对于多服务器或生产环境,应该使用 Redis 等外部存储。
在实现代理逻辑时,我们常常需要根据不同的条件来控制是否执行某些功能。例如,可以判断请求的路径、请求头、IP 地址、用户设备类型等,然后根据判断结果选择执行或跳过特定逻辑。 这样可以灵活地为不同场景定制代理行为,提高应用的适应性和可维护性。下面是一个常见的条件执行场景:判断请求的 user-agent 是否包含 Mobile,如果是移动设备就添加特定响应头,否则正常处理。
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function proxy(request: NextRequest) { // 检查是否是移动设备 const isMobile = request.headers.get('user-agent')?.includes('Mobile'); if (isMobile) { // 为移动设备添加特殊头 const
这个代理检查用户代理,如果是移动设备,就添加一个自定义头。目标路由可以根据这个头来调整行为。
代理逻辑会在每一次请求时被执行,因此对性能的要求非常高。如果在代理中实现了耗时的处理(例如频繁的数据库查询、大量文件读写或复杂的同步计算),会导致应用整体响应变慢,增加服务器压力。 建议将耗时操作移到缓存、异步处理、或者延迟执行,并尽量减少在代理逻辑内部的阻塞和重复计算。例如,通过内存缓存、Redis、或者 CDN 来减轻每次请求都执行相同操作的负担,从而显著提升整体性能。
如果必须执行耗时操作,应该使用缓存。下面是一个使用内存缓存的示例:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; // 简单的内存缓存 const cache = new Map<string, { value: any; expiry: number }>(); function getCached(key: string) { const item = cache.
这个例子使用内存缓存来存储耗时操作的结果,避免每次都执行。
虽然 Next.js 官方只允许一个 proxy.ts 文件,但如果你有多个需要依次处理请求的代理逻辑(例如鉴权、日志、重写、header 添加等),可以通过“链式”函数组合将这些处理流程串联起来。
每个处理函数接收请求对象,根据需要决定是否拦截请求并返回响应,否则就将请求传递给下一个处理函数。这样既让代理逻辑拆分清晰、便于维护,也可以灵活扩展不同的处理需求。
下面是具体的实现思路:
|// proxy.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; type ProxyHandler = (request: NextRequest) => NextResponse | null; function chain(...handlers: ProxyHandler[]) { return (request: NextRequest) =>
这个例子展示了如何组合多个代理处理函数。每个处理函数可以返回一个响应(如果它想拦截请求)或 null(如果它想让请求继续)。
如果你正在使用 Next.js 16 之前的版本,或者你的项目中已经有 middleware.ts 文件,你可能需要迁移到新的 proxy.ts。Next.js 提供了一个自动迁移工具来简化这个过程。
你可以运行以下命令来自动执行迁移:
|npx @next/codemod@latest middleware-to-proxy .
这个命令会自动:
middleware.ts 重命名为 proxy.tsmiddleware 函数重命名为 proxynext.config.ts 中的相关配置(例如将 skipMiddlewareUrlNormalize 改为 skipProxyUrlNormalize)需要注意的是,proxy 运行在 Node.js 运行时,不再支持 Edge 运行时。如果你需要 Edge 运行时的功能,可以继续使用已弃用的 middleware.ts,但建议尽快迁移到 proxy.ts。
在这一堂课中,我们介绍了 Next.js 的代理功能。我们学习了如何创建代理,如何修改请求和响应,如何实现身份验证、多语言支持、日志记录、速率限制等功能。
代理是 Next.js 中一个强大的工具,它让我们可以在请求处理的早期阶段执行各种逻辑。在 Next.js 16 中,原先的 middleware.ts 已被重命名为 proxy.ts,以更清晰地表示其在应用程序网络边界的作用。合理使用代理可以大大简化我们的代码,提升应用的安全性和性能。
在下一个部分,我们将学习样式和 CSS,了解如何在 Next.js 中使用各种 CSS 解决方案,包括 CSS Modules、Tailwind CSS 等,以及如何组织和优化样式代码。