动态 API 各一格
cookies / headers / connection / after 四个请求级 API 的活演示:前三个让所在格退出静态、必须待在 Suspense 洞内,after 在响应返回后收尾
cookies() — 读写本次请求的 Cookie
它让这一格退出静态:Cookie 按请求不同而不同,缓存的共享产物装不下它;写只能在 Server Action / Route Handler 里
headers() — 读取本次请求的请求头
同理退出静态:UA 按客户端不同而不同。换浏览器或用 curl 改 UA 重访,值随之变化
connection() — 声明「等到请求到达再渲染」
不读任何数据,只是一句声明;它之后取当前时刻、生成随机 ID 才是合法的(渲染期裸 new Date() 会被冻结在构建期)
after() — 响应返回后的收尾任务
写日志、上报指标这类「不该拖慢响应」的工作;不读请求数据,不受 Suspense 规则约束,也不参与渲染缓存
为什么前三个必须待在 Suspense 洞内
cacheComponents 的核心交换:显式声明动态性,换取其余部分的预渲染
- 预渲染的产物是「所有人共享的一份」;cookies()/headers()/connection() 读的都是「本次请求」,放进静态产物就会把某个请求的数据发给所有人 —— 所以框架强制它们包在 Suspense 里,构建期预渲染到洞口为止, 洞的内容留到请求时流式补出(即 PPR 的静态壳 + 动态洞)
- 裸放在页面顶层会在构建期报 blocking-route 错误 —— 这不是限制,是框架替你挡住了「千人千面数据被共享缓存」这类事故
- 本页自己就是证据:页头、说明文字、四个分区的框架是静态壳 (构建期一次产出),三个面板是洞(每请求现算), 刷新时先看到壳和占位,洞随后到达
- 反例:after() 不读请求、不产渲染内容,只是响应后的任务通道, 所以它在 Route Handler / Server Action / Server Component 里 都能直接用,与 Suspense 无关
draftMode():只做了解
本站不做活演示的原因,本身就是个知识点
draftMode() 让特定请求绕过缓存看到草稿内容(CMS 预览场景), 本质上是「按 cookie 切换的又一种动态 API」。它必须有真实的 「草稿 vs 已发布」两套数据源才有意义,本站的 mock 数据没有 这一层,硬做只会演示一个永远开着的开关。需要时直接看官方文档。