# next源码主线:从请求到渲染
# 这篇怎么读
目标不是覆盖所有源码,而是建立“可追踪主线”:
一次 HTTP 请求进入 Next 后,如何路由、如何渲染、如何输出。
阅读目标建议定成三层:
- 流程层:知道每一步做什么
- 职责层:知道每层模块负责什么
- 调试层:知道如何验证你的理解
# 总体链路(概念)
- Node/Edge 运行时接收请求
- 路由匹配(App Router segment)
- 组装渲染上下文(headers/cookies/params)
- 执行 Server Components
- 生成 HTML 与 RSC Payload
- 返回流式响应,客户端接管
如果用一句话概括:
Next 在服务端先渲染“可见内容”,再通过 Flight 把“组件语义”传给客户端恢复。
# 关键模块(阅读抓手)
下述是“职责级地图”,便于源码定位时不迷路。
- 路由解析:负责把 URL 映射到 app 目录下的 segment 树
- 渲染入口:创建 render context,并驱动 React 服务端渲染流程
- Flight 序列化:把服务端组件树结果编码成客户端可恢复的数据流
- 响应层:把 HTML 与 RSC 片段按流式方式输出
# 建议的源码入口顺序(避免迷路)
- 先看请求处理入口(dev/prod 各有分支)
- 再看 app-router 的渲染入口
- 然后看 Flight stream 的编码与输出
- 最后看客户端恢复链路(hydrate + router state)
不要一上来就追所有 util 文件,会陷入细节噪声。
# App Router 的核心理解
在 App Router 下,你看到的是目录树;Next 内部看到的是“segment 图 + 边界信息”:
layout:共享外壳page:叶子内容loading:异步边界占位error:错误边界
这决定了为什么 Next 能做分段流式返回,而不是等整页算完。
# 对应到源码认知
你在目录里看到的是文件树,框架内部会把它构造成“可渲染树 + 边界元信息”:
- 哪些边界可提前输出
- 哪些边界需要等异步数据
- 哪些边界失败后走 error/fallback
这正是 streaming 行为稳定可控的基础。
# 为什么要输出 RSC Payload
只输出 HTML 不够,因为客户端还要知道组件树结构与边界状态。
RSC Payload(Flight)承担了“让客户端恢复同构视图”的桥梁角色。
你可以理解为:
- HTML:给浏览器立即可见内容
- Flight:给 React 客户端恢复组件语义
# 深一点的理解
Flight 不是“再传一份 HTML”,而是传“组件引用 + props + 边界状态”的可恢复描述。
这让客户端不需要重跑整套服务端逻辑,也能接上同一棵 UI 语义树。
# Streaming 的工程价值
# 没有 streaming
后端必须等慢接口全部完成,TTFB 和首屏都被拖慢。
# 有 streaming
可先返回壳 + 已完成片段,慢块后续补齐。
用户“先见内容,再逐步完整”,体感明显更好。
# 关键取舍
- streaming 提升感知速度,但也会增加调试复杂度
- 边界划分太碎会增加心智成本
- 边界划分太粗会回退到“整页等待”
资深实践里,通常按“首屏关键区 / 非关键区”切分 Suspense,而不是按组件颗粒度随意切。
# 请求链路中的缓存决策点
源码层你要重点关注“何时读取缓存、何时判定失效”:
- 数据请求层(fetch cache)
- 路由响应层(route segment cache)
- 重验证触发点(time/tag/path)
理解这些点后,线上“为什么这页没更新”问题才有排查路径。
# 源码阅读建议(按场景)
建议从这三个场景切入追源码:
- 纯静态页面请求
- 含
fetch + revalidate的页面请求 - 含
loading.tsx + Suspense的流式页面请求
每个场景都回答 4 个问题:
- 路由如何命中
- 数据何时读取
- 响应何时首字节输出
- 客户端如何接管
# 再加 2 个问题(资深视角)
- 缓存命中发生在哪一层
- 出错后 fallback 与重试在哪个阶段触发
# 一个最小实验(建议你本地跑)
建一个页面里串两个请求:
- 快请求(50ms)
- 慢请求(1500ms,放在 Suspense 里)
然后抓包看响应时间线,你会直观看到流式分段输出。
建议同时打开:
- 浏览器 Network(看分段返回)
- Server 日志(看各数据源耗时)
- React DevTools(看 hydration 边界)
把三者对齐,你对“框架行为”的理解会非常扎实。
# 调试技巧:把框架黑盒变白盒
# 1)打点策略
在关键数据函数、layout/page 入口、route handlers 统一加 requestId 打点。
同一个 requestId 串起服务端日志与前端 waterfall。
# 2)构造故障注入
主动注入:
- 慢接口(sleep 2s)
- 失败接口(500)
- 缓存失效延迟
观察 loading/error 边界与响应分段是否符合预期。
# 3)做“冷/热缓存”对照
同一路由分别测:
- 冷缓存首访
- 热缓存复访
这能直接验证你的 revalidate/tag 方案是否真的生效。
# 案例:商品详情页的请求链路拆解
页面结构:
layout:站点壳page:商品基本信息(可缓存)Suspense A:价格库存(实时)Suspense B:推荐商品(弱实时)
链路收益:
- 首屏先回基础信息和骨架
- 实时区块后续补齐
- 推荐区块可高缓存命中
这种拆法在电商、内容社区、SaaS 控制台都适用。
# 小结
Next 源码阅读最怕“横向泛读”。
更有效的方法是:拿请求链路做主线,按场景纵向跟踪。
抓住“路由 -> RSC -> Streaming -> Hydration”四段,理解会非常稳定。