next-cache-components-adoption

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

next-cache-components-adoption

Next.js缓存组件适配指南

Enable Cache Components on an app and walk it to a clean build. This skill sequences the work; per-error recipes live in the dev overlay fix cards, stack traces, and
/docs/messages/blocking-prerender-*
pages.
在应用中启用Cache Components并完成无错误构建。本技能按顺序安排工作;针对具体错误的解决方案可在开发覆盖层的修复卡片、堆栈跟踪以及
/docs/messages/blocking-prerender-*
页面中找到。

requires

前置要求

App Router only. Cache Components is an App Router feature;
cacheComponents: true
does nothing for
pages/
routes. If the project has a
pages/
or
src/pages/
tree but no
app/
or
src/app/
tree, stop and tell the user — Pages → App migration is its own project, not part of this skill. A hybrid app (both
pages/
and
app/
) is fine: the flag affects the
app/
routes;
pages/
routes are unaffected and don't need opt-outs.
Next.js 16.3+. That release is where the pieces this skill relies on land: top-level
cacheComponents
,
export const instant
, the dev-overlay instant-navigation validation warnings (including
link-prefetch-partial
), and the
cache-components-instant-false
codemod.
If
next --version
reports below 16.3, upgrade first:
  • npx @next/codemod@latest upgrade latest
    to apply the version-to-version codemods.
  • Read the relevant version upgrade guide (e.g. Version 16) for what the codemod doesn't cover.
This skill also assumes a clean starting point. If the app still uses
experimental.dynamicIO
/
experimental.useCache
, route segment configs (
dynamic
,
revalidate
,
fetchCache
), or
unstable_cache()
, work those out first via the migration guide, then come back here.
If the app already uses
"use cache"
, there is no green build before
cacheComponents: true
— the pre-flag build errors with
please enable the feature flag cacheComponents
. Enabling the flag is the first step of milestone A, not a thing to do after getting green; the green baseline comes from milestone A (blanket the opt-outs in), not from before it. Note this in your starting summary so it doesn't read as a regression.
Offline copies of guide links live under
node_modules/next/dist/docs/
, with the directory layout numbered for ordering (e.g.
node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md
). The trailing filename matches the slug. If you can't predict the numbered prefix,
find node_modules/next/dist/docs -name '<slug>.md'
resolves it. The
/docs/messages/*
error pages are not bundled. If offline docs are missing entirely, run
npx @next/codemod@latest agents-md
to write a version-matched index into
AGENTS.md
/
CLAUDE.md
.
仅支持App Router。Cache Components是App Router的功能;
cacheComponents: true
pages/
路由无效。如果项目包含
pages/
src/pages/
目录树,但没有
app/
src/app/
目录树,请告知用户——从Pages Router迁移到App Router是独立项目,不属于本技能的范畴。混合应用(同时包含
pages/
app/
)是允许的:该标志仅影响
app/
路由;
pages/
路由不受影响,无需排除。
Next.js 16.3+。该版本包含本技能依赖的所有功能:顶层
cacheComponents
配置、
export const instant
、开发覆盖层的即时导航验证警告(包括
link-prefetch-partial
),以及
cache-components-instant-false
代码转换工具。
如果
next --version
显示版本低于16.3,请先升级:
  • 运行
    npx @next/codemod@latest upgrade latest
    来应用版本间的代码转换。
  • 阅读对应的版本升级指南(例如版本16),了解代码转换工具未覆盖的内容。
本技能还假设项目处于干净的起始状态。如果应用仍在使用
experimental.dynamicIO
/
experimental.useCache
、路由段配置(
dynamic
revalidate
fetchCache
)或
unstable_cache()
,请先通过迁移指南处理这些内容,然后再回到本技能。
如果应用已使用
"use cache"
,那么在设置
cacheComponents: true
之前无法构建成功——设置标志前的构建会报错
please enable the feature flag cacheComponents
。启用该标志是里程碑A的第一步,而不是在构建成功后再做的事;绿色基线来自里程碑A(全面添加排除配置),而不是之前的状态。请在起始总结中注明这一点,避免被视为回归问题。
指南链接的离线副本位于
node_modules/next/dist/docs/
目录下,目录结构按编号排序(例如
node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md
)。文件名后缀与slug匹配。如果无法预测编号前缀,可以运行
find node_modules/next/dist/docs -name '<slug>.md'
来查找。
/docs/messages/*
错误页面未被打包。如果完全缺少离线文档,可以运行
npx @next/codemod@latest agents-md
将匹配版本的索引写入
AGENTS.md
/
CLAUDE.md

the shape of the work

工作流程

Adoption has two milestones. Each is shippable on its own:
  • A. Green build.
    next build
    passes with
    cacheComponents: true
    — blanket
    instant = false
    if needed. Setup for B. (steps 1–2.)
  • B. Remove
    instant = false
    .
    This is the loop where adoption happens. Walk the route tree top-down, one subtree at a time, removing each opt-out and either making the route prerenderable or documenting it as a deliberate Block — checking in with the user at each subtree boundary. Expect to spend most of the time here. (steps 2–3.)
Adoption is complete after B. Further optimization — making navigations instant, adopting Partial Prefetching, locking the result in with e2e tests, growing static shells — is covered by the linked guides in further reading. Point the user at them; this skill doesn't walk through them.
End of every milestone: summarize and ask. Tell the user which routes changed and how (cached / wrapped in
<Suspense>
/ opted out as a documented Block), what they should sanity-check, and ask whether to open a PR before continuing. Each milestone is a real checkpoint, not a step inside one agent run. Don't silently roll on.
适配过程分为两个里程碑,每个里程碑的成果都可独立发布:
  • A. 无错误构建。设置
    cacheComponents: true
    next build
    执行成功——必要时全面添加
    instant = false
    配置。为里程碑B做准备(步骤1-2)。
  • B. 移除
    instant = false
    配置
    这是完成适配的核心环节。自上而下遍历路由树,每次处理一个子树,移除每个排除配置,要么使路由可预渲染,要么将其标记为故意阻塞并记录——在每个子树边界处与用户确认。预计大部分时间会花费在此环节(步骤2-3)。
完成里程碑B即表示适配完成。进一步优化——实现即时导航、采用部分预取、通过端到端测试锁定结果、扩展静态外壳等——可参考延伸阅读中的链接指南。请引导用户查看这些内容;本技能不会详细讲解这些优化步骤。
每个里程碑结束时:总结并确认。告知用户哪些路由发生了变更以及变更方式(缓存/包裹在
<Suspense>
中/标记为已记录的阻塞路由),他们需要检查哪些内容,并询问是否要在继续之前创建PR。每个里程碑都是真实的检查点,而非单次Agent运行中的一个步骤。不要静默继续。

background

背景知识

cacheComponents: true
requires every route to be prerenderable. A route that reads request-time data outside
<Suspense>
is "blocking" and fails the build.
export const instant = false
marks a route as allowed to block, which clears it in both dev and build; on a layout it covers the whole subtree beneath it.
instant = false
does not clear sync-IO errors.
Unstable values evaluated at module/render time —
new Date()
,
Date.now()
,
Math.random()
,
crypto.randomUUID()
— still fail the prerender (
blocking-prerender-current-time
/
-random
/
-crypto
) even with the opt-out, because they produce a different result on every render and can't be baked into a static shell. So the blanket codemod gets the build green only if no shared layout or page calls one of these directly; if one does, you must fix it regardless of
instant = false
. Follow the fix cards on the error page itself — they own the per-API recipe. This most often bites in a shared layout, where one
new Date()
blocks every route under it.
cacheComponents: true
要求每个路由都可预渲染。在
<Suspense>
之外读取请求时数据的路由会被视为“阻塞”,并导致构建失败
export const instant = false
标记路由允许阻塞,这会在开发和构建阶段清除错误;如果在布局中设置,会覆盖其下的整个子树。
instant = false
无法清除同步IO错误
。在模块/渲染阶段计算的不稳定值——
new Date()
Date.now()
Math.random()
crypto.randomUUID()
——即使设置了排除配置,仍会导致预渲染失败(错误码
blocking-prerender-current-time
/
-random
/
-crypto
),因为它们每次渲染都会产生不同的结果,无法被固化到静态外壳中。因此,只有当没有共享布局或页面直接调用这些API时,全面代码转换才能使构建成功;如果有调用,无论是否设置
instant = false
,都必须修复。请遵循错误页面上的修复卡片——它们包含针对每个API的解决方案。这种情况最常发生在共享布局中,一个
new Date()
调用会阻塞其下的所有路由。

surfacing errors

错误排查

Two surfaces; they show different things.
next build
— detection only.
Use it to confirm milestone A (green build) and to spot-check milestone B (no route opted out). It stops at the first blocking route, so it's poor for sizing the work. Two flags help when iterating:
--debug-build-paths
builds only the routes you name (comma-separated glob patterns of file paths relative to the project root, e.g.
--debug-build-paths="app/admin/**/page.tsx"
or
--debug-build-paths="app/(marketing)/about/page.tsx"
— not URL paths;
--debug-build-paths=/admin
matches nothing and silently exits 0) and
--debug-prerender
(dev-only) prints a fuller stack trace so the error names the originating file and line.
next dev
— the working surface.
Visit a route; its blocking errors surface in the dev overlay with full stack traces and fix cards linking the per-error docs. Work one route at a time — errors don't accumulate in one place. The route itself still returns HTTP 200, so don't gate on status codes; read the overlay (or
.next-dev.log
if you can't drive a browser yet).
Verifying a fix at runtime. A green build or a cleared overlay isn't proof the route actually behaves — Cache Components is a runtime concern (a static shell with streamed data). Load the route in a real browser, wait for streaming to settle, and confirm it renders. Three ways, in order of preference:
  1. The
    next-dev-loop
    skill
    is the fastest path: it cross-checks
    /_next/mcp
    against the live browser. Install if your agent doesn't have it:
    bash
    npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop
    It has its own hard prerequisites (Turbopack and
    agent-browser >= 0.27.0
    ) and will tell you how to set those up.
  2. A browser you can drive yourself (Playwright, agent-browser, any browser-automation tool).
    next-dev-loop
    is an accelerator, not a prerequisite.
  3. No browser at all? Ask the user. Either ask them to drive the dev server and report what the overlay shows, or commit the milestone you've reached and hand off. Don't silently stop at A or B and call it done — the runtime behavior won't surface in the build. Be explicit about what you couldn't verify.
Verify after every fix, not only at the end. Don't fall back to grepping source or trusting the build alone.
有两种排查方式,它们显示的内容不同。
next build
——仅检测错误
。用于确认里程碑A(无错误构建)和抽查里程碑B(无路由被排除)。它会在遇到第一个阻塞路由时停止,因此不适合评估工作量。迭代时有两个有用的标志:
--debug-build-paths
仅构建指定的路由(相对于项目根目录的文件路径的逗号分隔通配符模式,例如
--debug-build-paths="app/admin/**/page.tsx"
--debug-build-paths="app/(marketing)/about/page.tsx"
——不是URL路径;
--debug-build-paths=/admin
不会匹配任何内容并静默退出,返回码0);
--debug-prerender
(仅开发环境)会打印更完整的堆栈跟踪,以便错误信息包含源文件和行号。
next dev
——主要工作界面
。访问路由时,阻塞错误会在开发覆盖层中显示,包含完整的堆栈跟踪和指向错误文档的修复卡片。一次处理一个路由——错误不会集中显示在一处。路由本身仍会返回HTTP 200,因此不要根据状态码判断;请查看覆盖层(如果无法操作浏览器,可查看
.next-dev.log
)。
在运行时验证修复效果。无错误构建或覆盖层错误清除并不代表路由实际行为正常——Cache Components是运行时相关功能(带有流式数据的静态外壳)。在真实浏览器中加载路由,等待流传输完成,确认渲染正常。以下三种方式按优先级排序:
  1. next-dev-loop
    技能
    是最快的方式:它会交叉校验
    /_next/mcp
    和实时浏览器的内容。如果Agent未安装该技能,请安装:
    bash
    npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop
    它有自己的硬性前置要求(Turbopack和
    agent-browser >= 0.27.0
    ),并会告知如何设置这些要求。
  2. 可自行操控的浏览器(Playwright、agent-browser或任何浏览器自动化工具)。
    next-dev-loop
    是加速工具,不是必需项。
  3. **完全没有浏览器?**询问用户。要么请他们操控开发服务器并报告覆盖层显示的内容,要么提交当前已完成的里程碑并移交。不要在里程碑A或B处静默停止并声称已完成——运行时行为不会在构建中体现。请明确说明无法验证的内容。
每次修复后都要验证,而不仅仅是在最后。不要依赖 grep 源代码或仅信任构建结果。

step 1: choose a strategy

步骤1:选择策略

Ask the user; don't assume. In a non-interactive run (no way to prompt), default to Blanket for a multi-route app and Direct for a single-route or handful-of-routes app, and say so when you start.
  • Blanket — run the codemod to opt every page and layout out, get a clean build immediately, merge that, then remove the opt-outs feature by feature in follow-up PRs. Use for large apps, team repos (a long-lived failing branch blocks others), or when you can't land every route in one PR.
  • Direct — enable the flag and fix every route in place in one pass. Use for small or solo apps where one PR is realistic.
询问用户,不要假设。在非交互式运行中(无法提示),多路由应用默认选择全面排除策略,单路由或少量路由应用默认选择直接修复策略,并在开始时说明这一点。
  • 全面排除——运行代码转换工具将所有页面和布局排除,立即获得干净的构建结果,合并该变更,然后在后续PR中逐个移除排除配置。适用于大型应用、团队仓库(长期失败的分支会阻碍其他人),或无法在一个PR中完成所有路由适配的场景。
  • 直接修复——启用标志并一次性就地修复所有路由。适用于小型或个人应用,可通过一个PR完成适配。

blanket

全面排除

bash
npx @next/codemod@canary cache-components-instant-false ./app
Inserts
export const instant = false
(with a
// TODO: Cache Components adoption
comment) into every
app/**/{page,layout,default}
file, skipping files that already declare
instant
and any module marked
"use client"
or
"use server"
. Then set
cacheComponents: true
. The TODO comments are the work queue for milestone B.
If the codemod isn't available (older
@next/codemod
, sandboxed environment, offline run), reproduce it by hand: for every
app/**/{page,layout,default}.{js,jsx,ts,tsx}
that isn't
"use client"
or
"use server"
and doesn't already declare or export
instant
in any form, insert the three-line block below after the file's import statements (or at the top, if there are none):
ts
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false
Then set
cacheComponents: true
. The result is the same as what the codemod produces.
The codemod opts every segment out, not only the root, on purpose. Resolution is top-down, first-explicit-config-wins: the highest
instant = false
in a route's tree decides the whole subtree, and deeper ones are never read. If you only opted the root layout out, removing it would re-arm validation for the entire app at once. With an opt-out on every segment, removing one segment's opt-out validates only that segment — its descendants keep their own opt-outs and stay green, so the blast radius is one segment at a time.
Because the highest opt-out wins, remove them top-down (root first, then descend). Removing a leaf's opt-out does nothing while an ancestor still holds one.
Confirm milestone A with a build. Run
next build
and make sure it completes with no blocking-route errors before you call the green build done. The codemod gets you most of the way, but a shared layout that calls
new Date()
/
Math.random()
directly still fails regardless of the opt-out (see "background" above), so the build is the proof, not the codemod run.
After running the codemod, confirm the root layout got an opt-out (
grep -n "export const instant" app/layout.*
). The root layout is the one segment that must be covered: it renders every route, including framework routes like
/_not-found
, so if it still reads
cookies()
without an opt-out the build fails on
/_not-found
even though no other route changed. If it was missed, add
export const instant = false
to it by hand.
Never add
instant = false
to a synthetic route
like
/_not-found
— there is no user file for it, and the directive wouldn't apply. When
/_not-found
(or another framework route) blocks, the cause is the root layout it renders through; fix the opt-out there.
Client Components (
"use client"
pages/layouts) get no opt-out
— the codemod skips them on purpose.
instant
is a Server Component route segment config; exporting it from a client module is a build error (
E1344
). They don't need one anyway: a client page is covered by its nearest server layout's opt-out, and a client page can't read server request data (
cookies()
,
headers()
,
await params
) itself, so it rarely blocks on its own. If a route with a client page still blocks, the cause is server-side data in an ancestor layout — fix the opt-out or the read there, not on the client page.
bash
npx @next/codemod@canary cache-components-instant-false ./app
在每个
app/**/{page,layout,default}
文件中插入
export const instant = false
(附带
// TODO: Cache Components adoption
注释),跳过已声明
instant
的文件以及标记为
"use client"
"use server"
的模块。然后设置
cacheComponents: true
。TODO注释是里程碑B的工作队列。
如果代码转换工具不可用
@next/codemod
版本较旧、沙箱环境、离线运行),可手动实现:对于每个未标记
"use client"
"use server"
且未以任何形式声明或导出
instant
app/**/{page,layout,default}.{js,jsx,ts,tsx}
文件,在文件的导入语句之后(如果没有导入语句则在顶部)插入以下三行代码:
ts
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
// See: https://nextjs.org/docs/app/guides/migrating-to-cache-components
export const instant = false
然后设置
cacheComponents: true
。结果与代码转换工具生成的内容一致。
代码转换工具会将每个路由段排除,而非仅根路由段,这是故意设计的。解析是自上而下的,以第一个明确的配置为准:路由树中层级最高
instant = false
决定整个子树,更深层级的配置不会被读取。如果仅排除根布局,移除它会立即重新启用整个应用的验证。而在每个路由段都设置排除配置后,移除一个路由段的排除配置只会验证路由段——其子路由段仍保留自己的排除配置并保持构建成功,因此影响范围仅限于单个路由段。
由于层级最高的排除配置优先级最高,应自上而下移除(先根路由,再子路由)。在祖先路由段仍保留排除配置时,移除叶子路由段的排除配置不会产生任何效果。
通过构建确认里程碑A。运行
next build
,确保完成时没有阻塞路由错误,再宣布无错误构建完成。代码转换工具能解决大部分问题,但直接调用
new Date()
/
Math.random()
的共享布局仍会失败,无论是否设置排除配置(见上文“背景知识”),因此构建结果才是最终证明,而非代码转换工具的运行结果。
运行代码转换工具后,确认根布局已添加排除配置(运行
grep -n "export const instant" app/layout.*
)。根布局是必须覆盖的路由段:它渲染所有路由,包括
/_not-found
等框架路由,因此如果它仍在未设置排除配置的情况下读取
cookies()
,即使其他路由未变更,构建也会在
/_not-found
处失败。如果根布局被遗漏,请手动添加
export const instant = false
切勿向
/_not-found
等合成路由添加
instant = false
——没有对应的用户文件,该指令也不会生效。当
/_not-found
(或其他框架路由)阻塞时,原因是它渲染时经过的根布局;请在根布局中修复排除配置或数据读取问题。
客户端组件(
"use client"
页面/布局)无需排除配置
——代码转换工具会故意跳过它们。
instant
是服务端组件的路由段配置;从客户端模块导出它会导致构建错误(
E1344
)。它们也不需要:客户端页面会被最近的服务端布局的排除配置覆盖,且客户端页面本身无法读取服务端请求数据(
cookies()
headers()
await params
),因此很少会自行阻塞。如果包含客户端页面的路由仍阻塞,原因是祖先布局中的服务端数据;请在祖先布局中修复排除配置或数据读取问题,而非客户端页面。

direct

直接修复

Set
cacheComponents: true
and collect the errors. The reported routes are the work queue; there are no opt-outs to remove.
设置
cacheComponents: true
并收集错误。报告的路由即为工作队列;无需添加排除配置。

step 2: remove opt-outs, one subtree at a time

步骤2:逐个移除排除配置

The route tree is the work queue. Pick one subtree (
app/dashboard/**
, or a top-level app if the repo has several — marketing, app, docs), finish it end-to-end, ship it, then start the next. Each subtree is an independent, mergeable change. Don't fan out across the whole app in one pass — the point of milestone A's blanket was to make the loop incremental, not optional.
Within a subtree, walk top-down (layouts before the pages beneath them, root layout first). The root layout is often the hardest (it wraps
<html>
/
<body>
and frequently reads
cookies()
), but it shadows every route including framework routes like
/_not-found
, so it has to come off before anything below it can be validated. (Direct path: there are no opt-outs to remove — fix each failing route; if a hand-written opt-out on an ancestor shadows it, remove that first.)
A green build mid-walk doesn't mean the layout is clean. Removing a layout's opt-out while its descendant pages still have theirs keeps the build green — each page shadows the inherited validation. The layout's actual blocking reads only surface once nothing below it shadows them. So after a layout is opt-out-free, keep going down the subtree; if the layout has an inherent blocker, the first page you uncover will be the one to surface it. Don't call a subtree done at the layout boundary.
For each route in the subtree:
  1. Remove its
    instant = false
    (Blanket) or target the failing route (Direct).
  2. Reload it in dev or rebuild only that route. If it's clean, the route was already prerenderable — move on.
  3. If it still blocks, read the error in the dev overlay and apply the fix it points at. When the call gets ambiguous — you're not sure which fix fits, the blocking code looks security-sensitive, or the user might want to keep the route blocking on purpose — read references/per-page-decisions.md before editing. Those cases are user check-in moments, not agent judgment calls.
  4. Re-check the route. If your fix touched shared code (a layout, a sidebar component), re-check sibling routes too — a shared-shell change can fix the route you're on and break a sibling. Then move to the next route.
Keep a todo list of the subtree's routes and work it to completion; don't truncate. When every route in the subtree is clean, move to step 3 to verify and hand the subtree off to the user.
路由树即为工作队列。选择一个子树(例如
app/dashboard/**
,或仓库中的一个顶级应用——营销、主应用、文档),从头到尾完成适配,发布变更,然后开始下一个子树。每个子树都是独立的、可合并的变更。不要在一次操作中覆盖整个应用——里程碑A的全面排除策略就是为了让这个环节可增量进行,而非可选。
在子树中,自上而下遍历(先布局,再其下的页面,先根布局)。根布局通常是最难的(它包裹
<html>
/
<body>
且经常读取
cookies()
),但它会覆盖所有路由,包括
/_not-found
等框架路由,因此必须先移除其排除配置,才能验证其下的任何路由。(直接修复路径:无需移除排除配置——修复每个失败的路由;如果祖先路由段的手动排除配置覆盖了它,先移除该配置。)
遍历过程中的无错误构建不代表布局已清理完成。移除布局的排除配置时,如果其子页面仍保留排除配置,构建仍会成功——每个页面都会覆盖继承的验证。只有当布局下没有任何路由段覆盖验证时,布局的实际阻塞读取才会暴露。因此,布局移除排除配置后,继续向下遍历子树;如果布局存在固有阻塞问题,第一个被暴露的页面会显示错误。不要在布局边界处宣布子树适配完成。
对子树中的每个路由:
  1. 移除其
    instant = false
    配置(全面排除策略)或定位失败的路由(直接修复策略)。
  2. 在开发环境中重新加载路由或仅重新构建该路由。如果无错误,说明路由已可预渲染——继续下一个。
  3. 如果仍阻塞,请查看开发覆盖层中的错误并应用其指向的修复方案。当情况不明确时——不确定哪种修复方案适用、阻塞代码看起来涉及安全敏感内容,或用户可能希望故意保留路由阻塞——在编辑前请阅读**references/per-page-decisions.md**。这些情况需要与用户确认,而非由Agent自行判断。
  4. 重新检查路由。如果修复涉及共享代码(布局、侧边栏组件),请同时重新检查兄弟路由——共享外壳的变更可能修复当前路由,但会破坏兄弟路由。然后继续下一个路由。
保留子树路由的待办列表,直到全部完成;不要中途停止。当子树中的所有路由都无错误时,进入步骤3验证并将子树移交用户。

step 3: verify the subtree

步骤3:验证子树

A checklist, not new adoption work. This is where the user signs off on the subtree before you start the next.
  • next build
    completes without blocking-route errors.
  • No bare
    // TODO: Cache Components adoption
    opt-outs are left in the subtree (
    grep
    to confirm). Any
    instant = false
    left behind must be a deliberate, documented Block — its comment rewritten to a reason (see references/per-page-decisions.md → "when to leave a Block in place"), not the original
    // TODO
    .
  • Drive each route in dev, not only the build. Visit it, wait for streaming to settle, confirm every
    <Suspense>
    fallback resolves to its real content (not stuck on a skeleton or a blank). A green build with zero opt-outs is not the same as a working route. Query the live DOM if a tool's snapshot looks stale before reporting a route as broken.
  • Show the user the rendered result. A screenshot or the visible content you observed, per route. The build can't tell whether the streamed-in loading state, the fallback, or the final layout matches what the user wants. Adoption changes the experience, so the person who owns the product should sign off on each piece.
Expect some routes to still print
ƒ
(Dynamic) in the build's route table — that is success, not a regression.
A route comes out
ƒ
when it does request-time work through the documented escape hatch (e.g. a layout that
await connection()
for
new Date()
); the page is no longer opted out, it is genuinely dynamic. Don't rip the escape hatch back out chasing a
. The inverse also holds:
instant = false
does not force a route to be
ƒ
. The glyph reflects what the route does at prerender time, not which validation knobs it exports.
When the subtree passes and the user is happy with each route, summarize and ask: open a PR and move to the next subtree, or stop here?
Milestone B is done only when every subtree is clean — every remaining
instant = false
sits under a reason comment, no bare TODOs are left (
grep -rln "TODO: Cache Components adoption" app
returns nothing). Adoption is complete here. Point the user at further reading if they want to push the experience further, or stop and ship.
这是一个检查清单,而非新的适配工作。用户需要在此环节确认子树适配完成,才能开始下一个子树。
  • next build
    完成时无阻塞路由错误。
  • 子树中没有遗留未处理的
    // TODO: Cache Components adoption
    排除配置(通过
    grep
    确认)。任何遗留的
    instant = false
    都必须是故意的、已记录的阻塞路由——其注释需修改为具体原因(见references/per-page-decisions.md → “何时保留阻塞路由”),而非原始的
    // TODO
  • 在开发环境中访问每个路由,而非仅依赖构建。访问路由,等待流传输完成,确认每个
    <Suspense>
    的回退内容都已解析为真实内容(未停留在骨架屏或空白状态)。无错误构建且无排除配置并不代表路由能正常工作。如果工具的快照看起来过时,请查询实时DOM后再报告路由损坏。
  • 向用户展示渲染结果。每个路由的截图或你观察到的可见内容。构建无法判断流式加载状态、回退内容或最终布局是否符合用户预期。适配会改变用户体验,因此产品负责人应确认每个部分。
部分路由在构建的路由表中仍显示
ƒ
(动态)是正常的,而非回归问题
。当路由通过已记录的逃逸舱口执行请求时工作(例如布局通过
await connection()
获取
new Date()
),路由会显示为
ƒ
;页面不再被排除,而是真正的动态路由。不要为了追求
而移除逃逸舱口。反之亦然:
instant = false
不会强制路由显示为
ƒ
。该符号反映的是路由在预渲染时的行为,而非其导出的验证配置。
当子树通过验证且用户对每个路由都满意时,总结并确认:创建PR并开始下一个子树,还是在此停止?
只有当每个子树都清理完成——所有遗留的
instant = false
都带有原因注释,没有未处理的TODO(
grep -rln "TODO: Cache Components adoption" app
无返回结果)——里程碑B才算完成。适配在此处完成。如果用户想要进一步优化体验,请引导他们查看延伸阅读,或停止并发布。

further reading

延伸阅读

Adoption ends at milestone B. The work below is optional and lives in the docs — link the user to them and let them decide which to take on next. Don't walk these through inside this skill.
  • Instant navigation — dev-only validation warnings the overlay raises on client navigation. Same shape as the blocking-prerender errors you cleared in step 2; the guide covers the per-warning details. Recommend it next if the user wants navigations to actually be instant (a green build doesn't guarantee that — a
    <Suspense>
    above the shared layout caught the page-load case but doesn't cover client navigation).
  • Adopting Partial Prefetching — walks an audit of
    <Link prefetch={true}>
    calls driven by the dev overlay's
    link-prefetch-partial
    warning, then flips the
    partialPrefetching
    config. Walk the audit first, with the flag off — flipping it before the audit makes every route count as adopted, so the warnings never fire and the per-link signal is lost. The biggest payoff of Cache Components:
    <Link>
    prefetches only the static App Shell by default. Recommended after instant navigation, since its fixes feed directly into how much of each route the shell can prefetch.
  • Prefetching and Runtime prefetching — broader prefetching reference. Runtime prefetching extends the static shell with per-session content; reach for it when a route's shell is too thin to be useful and Partial Prefetching alone doesn't cover the gap.
  • Locking the result in with e2e tests — the
    @next/playwright
    instant()
    helper asserts on the UI that's available immediately on navigation, so regressions surface in CI. Recommend it once a route is instant:
    next-dev-loop
    confirms it now; an
    instant()
    test keeps it that way.
  • next-cache-components-optimizer
    — a separate skill that grows each route's static shell so more of the page prerenders and less streams in. Pure optimization, not part of adoption.
适配在里程碑B结束。以下工作是可选的,相关内容在文档中——引导用户查看这些链接,让他们决定下一步要做什么。本技能不会详细讲解这些步骤。
  • 即时导航——开发环境中覆盖层在客户端导航时发出的验证警告。与步骤2中清除的阻塞预渲染错误格式相同;指南包含针对每个警告的详细内容。如果用户希望导航真正实现即时,建议接下来查看该指南(无错误构建不保证这一点——共享布局上方的
    <Suspense>
    处理了页面加载场景,但不覆盖客户端导航)。
  • 采用部分预取——根据开发覆盖层的
    link-prefetch-partial
    警告,审核
    <Link prefetch={true}>
    调用,然后设置
    partialPrefetching
    配置。先审核,再启用标志——在审核前启用标志会使所有路由被视为已适配,警告不会触发,从而丢失每个链接的信号。Cache Components最大的优势:
    <Link>
    默认仅预取静态应用外壳。建议在完成即时导航后查看,因为其修复内容直接影响外壳可预取的路由内容量。
  • 预取运行时预取——更全面的预取参考文档。运行时预取会将静态外壳扩展为包含会话内容;当路由外壳过于单薄且部分预取无法覆盖差距时,可使用该功能。
  • 通过端到端测试锁定结果——
    @next/playwright
    instant()
    助手会断言导航时立即显示的UI,因此回归问题会在CI中暴露。建议在路由实现即时导航后使用:
    next-dev-loop
    确认当前状态;
    instant()
    测试保持该状态。
  • next-cache-components-optimizer
    ——一个独立技能,用于扩展每个路由的静态外壳,使更多页面内容可预渲染,减少流式传输内容。这是纯优化,不属于适配范畴。