动态 API 各一格

cookies / headers / connection / after 四个请求级 API 的活演示:前三个让所在格退出静态、必须待在 Suspense 洞内,after 在响应返回后收尾

cookies() — 读写本次请求的 Cookie

它让这一格退出静态:Cookie 按请求不同而不同,缓存的共享产物装不下它;写只能在 Server Action / Route Handler 里

cookies()(请求时渲染中…)

headers() — 读取本次请求的请求头

同理退出静态:UA 按客户端不同而不同。换浏览器或用 curl 改 UA 重访,值随之变化

headers()(请求时渲染中…)

connection() — 声明「等到请求到达再渲染」

不读任何数据,只是一句声明;它之后取当前时刻、生成随机 ID 才是合法的(渲染期裸 new Date() 会被冻结在构建期)

connection()(请求时渲染中…)

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 数据没有 这一层,硬做只会演示一个永远开着的开关。需要时直接看官方文档。