Next 权衡录

把选型放到同一把尺子上

首页Server/Client 边界渲染策略路由机制数据与缓存Metadata 与 SEO工程化安全AI-Native 与 Agent

工程化

中间件边界、缓存策略设计与资产性能治理

Proxy 与中间件缓存策略设计Image/Font 与包体治理构建、测量与部署

Next 权衡录

把选型放到同一把尺子上

文档写能做什么。这里写该选哪条。

专题分类

  • Server/Client 边界
  • 渲染策略
  • 路由机制
  • 数据与缓存
  • Metadata 与 SEO
  • 工程化
  • 安全
  • AI-Native 与 Agent

资源

  • Next.js 文档
  • React 文档
  • Tailwind CSS
  • Vercel 部署
  • Next.js GitHub

© 2026 Next 权衡录 · Next.js 16 + React 19 + Tailwind CSS 4

8 个领域 · 37 个专题

工程化/Proxy 与中间件

Proxy 与中间件

Next 16 的 proxy.ts 固定跑在 Node;本站仍用 middleware.ts 做边缘改头演示,页面能读到同一枚请求头。matcher 之外的路由不会经过它

这一页读到的请求头

headers() 看到的是请求,不是 curl -I 里的响应。两边都写了同一个名字

x-playground-middleware: demo

读 headers() 会让这一页变成动态渲染。这是代价的一部分:要在渲染时看见请求级信息,就不能再把整页冻成静态 HTML。

活演示:响应头同时给 curl

包根 middleware.ts;matcher 只命中本页路径

// middleware.ts —— 包根,与 app/ 同级
import { NextResponse, type NextRequest } from "next/server";
import { DEMO_HEADER } from "@/lib/demo-header";

export function middleware(request: NextRequest) {
    // 请求头:页面里 headers() 读得到
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set(DEMO_HEADER, "demo");
    const response = NextResponse.next({
        request: { headers: requestHeaders },
    });
    // 响应头:curl -I 看得到。两个头不是同一个对象
    response.headers.set(DEMO_HEADER, "demo");
    return response;
}

// matcher:只有命中的路径才执行 middleware,其余路由零开销
export const config = {
    matcher: ["/engineering/middleware"],
};
# 生产构建后验证(dev 下同样生效):
curl -I http://localhost:3000/engineering/middleware
# HTTP/2 200
# x-playground-middleware: demo     ← 自定义头在这里

curl -I http://localhost:3000/
# HTTP/2 200
# (没有 x-playground-middleware)    ← 未命中 matcher,中间件未执行

源码见packages/next-playground/middleware.ts。页面上的字来自请求头;curl -I 看到的是响应头。运行时差异见下方「proxy.ts 与 middleware.ts」。

执行位置:请求管线的最前一站

在路由匹配、缓存查找、渲染之前执行;部署到边缘时离用户最近

  • 请求到达 → middleware → 路由匹配 → 缓存 → 渲染; 因此它适合做「入口级」决策:改写 URL、重定向、打头、粗鉴权
  • matcher 决定成本面:不写 matcher 等于每个请求 (含 _next/static、图片、favicon)都过一遍中间件; 本站收敛到单一路径就是为了让其余路由零开销
  • matcher 支持数组与否定模式,常见写法是"/((?!_next/static|_next/image|favicon.ico).*)"排除静态资产,只拦页面与 API

能做什么:改写 / 重定向 / 头部 / 鉴权门

四种职责都建立在「请求还没进渲染管线」这个事实之上

能力API典型场景
改写 rewriteNextResponse.rewrite()URL 不变、内容换源(多租户路径映射、A/B 实验分桶)
重定向 redirectNextResponse.redirect()未登录跳 /login、地区跳转、旧链接迁移
头部 headerNextResponse.next() 后改 headers本站演示;给下游传请求级上下文(实验分组、追踪 ID)
鉴权门 auth gate读 cookie / header 粗判后放行或拦截「有没有票」的门口检查,见下方代码
// ✅ 中间件做「门」:有无、过期的粗判,拦住明显未登录的请求
export function middleware(request: NextRequest) {
    const token = request.cookies.get("session")?.value;
    if (!token) {
        return NextResponse.redirect(new URL("/login", request.url));
    }
    return NextResponse.next(); // 放行,细粒度判断交给页面/接口
}

// ❌ 中间件别做「账」:权限矩阵、配额、行级数据归属这类判断
// 需要查库/读业务状态,放中间件里既慢又受运行时限制;
// 正确的位置是页面/Route Handler 内部,或受保护的数据访问层

Next 16:proxy.ts 与 middleware.ts

改名同时改了默认运行时。本站留着旧文件,是因为这则演示要跑在边缘上

  • proxy.ts 是新的文件约定,导出函数叫 proxy。它固定在 Node.js 运行时,不能再配 runtime
  • middleware.ts 仍能跑,默认还是 Edge,但已经废弃,构建时会警告。需要边缘上的轻量改写时,眼下仍用它
  • 两个文件不能同时存在。迁到 proxy 之后,依赖 Edge 冷启动的逻辑要重新量过延迟
  • matcher、NextResponse.next()、改头和重定向的写法两边相同。变的是运行时,不是 API 形状

什么时候别用 middleware

决策要点:每个匹配请求都要付一次执行成本,且运行时是受限的

  • 别读请求 body:中间件面向头部与路径工作, 读 body 会拖慢整条管线且 API 受限;需要 body 的判断放到 Route Handler / Server Action 里做
  • 别做重计算与数据库查询:middleware 通常运行在受限的 边缘运行时(无 Node 全量 API、冷启动计入每个请求), 一次查询放大成「每个匹配请求一次」
  • 鉴权只做「门」不做「账」:cookie 有无/过期的粗判放这里; 权限矩阵、配额、数据归属这类「账」必须有服务端权威校验, 中间件的放行永远不能替代页面/接口内的鉴权
  • 一个中间件能干的,不等于都该让它干: 能下沉到具体路由的 rewrite 规则(next.config.ts)优先用配置, 静态规则不需要运行时函数

延伸阅读

  • Next.js 文档:proxy.ts(原 middleware.ts,file conventions)