先搞清楚一件事:Next.js 不是构建工具,是运行时。区别在哪?构建工具只负责把代码打包,运行时则要对每一个请求做出裁决——走缓存,还是现算?这个裁决过程,才是它的灵魂。
说句难听的,很多人用了两年 Next.js,连它为什么快都不知道。快不是魔法,是协议。RSC 协议,本质上把组件树变成了一串可恢复的序列化流。你看到的是 标签,背后是特殊的前缀编码。就像电视信号,黑白到彩色不是加滤镜,是换了发射标准。

第一个关键词:流式渲染的代价
流式渲染想解决的问题很简单:服务器别憋大招。传统 SSR 必须等整个页面渲染完才吐出海量 HTML,TTFB 往往在 900ms 上下徘徊。Next.js 的做法是,先发一个壳,然后边算边补丁——用 Suspense 作为切分点,每个 chunk 独立产出。
听起来很美,但代价谁提了?流式响应会杀死 Nginx 的默认缓冲策略。如果你在 Nginx 后面没关 proxy_buffering,那流式渲染直接被卡成狗——数据全被攒在缓冲池里,直到页面算完才一次性给你。实测数据:关闭缓冲后,TTFB 从 1200ms 骤降到 240ms,但首字节之前的时间并没有变化——因为服务器该算的还是要算。这就像送外卖,你优化了送餐路线,但后厨出餐还是半小时。
这就是工程美学的第一个残酷真相:上层体验的平滑,往往是下层协议的牺牲换来的。你用了流式,就必须清理每一个反向代理节点。
第二个关键词:ISR 的数学逻辑
ISR 看起来像“静态页 + 定时器”,但底层是个贝叶斯更新过程。每次请求都触发一次判断:当前页面是否超过 revalidate 窗口?如果过期,就异步触发再生成,而这次请求返回的还是旧数据——注意,是故意的。
为什么?因为要保证响应时间恒定。你拿静态文件的速度 5ms,动态渲染 150ms,ISR 做的是:把动态计算挪到后台,让用户永远只等静态读取。但这里有个坑:如果首次生成还没完成,第二个并发请求来了,怎么办?Next.js 的默认行为是等待生成完成。这会导致一次长请求。我们用压测验证过:50 个并发请求,无预热状态下,200ms 到 14s 的延迟分布图就跟心电图似的。解决方案呢?手动调用 revalidatePath() 提前预热,或者用 partial dynamic 路由避开这个雷区。

第三个坑:不是所有东西都该上 RSC
RSC 限制了客户端上下文的使用。你不能在 Server Component 里用 useState,不能调用浏览器 API。于是很多人把所有组件改成 RSC,然后发现 UI 怎么不响应了。蠢不蠢?就好比把整个厨房改成电力系统,结果菜刀也用不了。
正确姿势:交互逻辑永远是 Client Component,RSC 只负责数据获取和纯展示。怎么区分?有个粗糙但实用的方法:这个组件有没有 onClick?有没有 useEffect?有,就别放 RSC。
再有就是 next/image 的优化不是免费的。它默认会把图片转成 WebP,用 Sharp 做锯齿重采样。但如果你部署在无 serverless 的 Node 环境,Sharp 二进制依赖会让你在 CI 阶段直接爆掉。我们踩过坑:Dockerfile 里必须加 libvips 相关依赖,否则 build 时给你报 139 信号崩溃——内存不够,不是代码错误。
性能对比:Streaming vs 传统 SSR

我们做了个简单的基准测试:同一台 4C8G 的机器,1000 个并发请求,页面含 20 个异步数据节点。传统 SSR 的 TTFB 是 1.2s,完整加载 2.8s;Next.js streaming 的 TTFB 是 0.3s,但完整加载时间差不多(2.5s)。所以结论是什么?流式渲染不是让页面变快,而是让用户“感觉”变快——First Paint 提前了 900ms,但整体吞吐量下降了 12%,因为每个 chunk 需要独立的连接和解析开销。看吧,没有银弹,只有权衡。
最后说说为什么我们仍然选 Next.js。因为它把复杂留给了自己,把选择留给了你。缓存模型、运行时、协议分离,每一项都是可插拔的。就像乐高,你没必须全用原装件,但基础砖块的精度决定了你能不能拼出硬核模型。
实践清单:三个可复用的避坑方案

- 代理缓冲必须显式关闭。在 Nginx 的 location 里加
proxy_buffering off;,Cloudflare 则要配置扫流解析。否则你流式了个寂寞。 - ISR 必须做预热。每次发布后,写个脚本调一次内部路由,让页面缓存先长出来,再放流量进来。我们叫它“cold start 免疫法”。
- 区分 RSC 与 Client 组件的边界。用 ESLint 插件
eslint-plugin-react-server-components强制规则,从编译期就掐死交互组件混入 RSC 的念头。
这三条我们每条都付出了生产事故的代价。第一条,上线后线上白屏 5 分钟,原因是 agent 缓存了空响应。第二条,高峰时段数据库被一次并发打满。第三条,用户画布组件在 RSC 里用了 localStorage,直接 hydration 失败。所有事故都是这么低级,但也这么真实。
Next.js 的哲学从来不是让你全盘接受,而是给你一套可手术的架构。前提是你真的理解协议,而不是被版本公告牵着走。别再迷信”一键优化“了,底层永远有你需要亲手收拾的烂摊子。