mcpField
N
next-cache-components-adoption

next-cache-components-adoption

github.com/vercel开发143.2k22.0k 次安装今天更新
代码仓库

关于 next-cache-components-adoption

用于在 Next.js App Router 项目中采用 Cache Components 并推动构建通过的技能。它引导逐路由修改,并通过开发服务器和浏览器核验运行行为及构建结果。

主要功能

  1. 01检查 App Router 与项目前置条件
  2. 02增量或直接采用的迁移路径
  3. 03逐路由迁移工作流
  4. 04通过 next dev 和浏览器检查运行行为
  5. 05路由修改后的构建验证

适用场景

  1. 01在 App Router 应用中启用 Cache Components
  2. 02迁移阻碍预渲染的路由
  3. 03诊断请求时数据和同步 I/O 问题

MCP 相关

该技能建议使用 next-dev-loop 进行运行时验证,该工具会使用 Next.js 的 `/_next/mcp` 端点和 agent-browser。技能本身未说明会提供 MCP 工具。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel/next.js --skill next-cache-components-adoption

安装后在 Claude Code 中输入/next-cache-components-adoption确认已加载。

附带文件references/dev-only-validations.mdreferences/per-page-decisions.md

详细介绍

next-cache-components-adoption

为应用启用 Cache Components,并逐步修复问题,直到构建通过。本技能会安排工作流程;针对各类错误的解决方案可在开发覆盖层修复卡片和构建终端输出中找到。迁移到 Cache Components 指南是本技能所用概念和各 API 解决方案的权威参考——每当技能步骤提到某种模式("use cache"、cacheLife、<Suspense> 的放置位置等),且你想了解完整说明时,请查阅该指南。

要求

  • App Router 项目。 Cache Components 是 App Router 功能;cacheComponents: true 对 pages/ 路由不起作用。如果项目有 pages/ 或 src/pages/ 目录,但没有 app/ 或 src/app/ 目录,请停下来告知用户——Pages → App 迁移是独立项目,不属于本技能的范围。混合应用(同时包含 pages/ 和 app/)没有问题:该标志仅影响 app/ 路由;pages/ 路由不受影响,也不需要退出该功能。

  • 已确定的应用目录。 首先查找 next.config.{js,ts,mjs,cjs}:该文件所在位置就是项目根目录;否则,从子目录调用的 Agent 可能会在错误的 cwd 下查找 app/,结果一无所获。在该目录下查找 app/ 和 src/app/,并将本技能中的所有命令和 glob 都视为相对于实际存在的目录。如果两者都存在,Next.js 会构建 app/,而不会查找 src/app/,因此后者的路由会被遮蔽且不会构建——请告知用户这一点,并询问要迁移哪个目录,而不要自行选择。

  • 可运行的应用。 整个流程需要通过 next dev 和浏览器进行验证,因此应用必须能够启动。如果应用在导入时会读取数据库或必需的环境变量(例如缺少 DATABASE_URL 时会抛出错误的 env.ts),请在步骤 1 前确认应用确实能够启动——使用实际环境,或搭建本地数据环境。无法运行的应用无法验证是否成功启用该功能。

  • Next.js 16.3 或更高版本。 本技能依赖的功能均从该版本开始提供:顶层配置 cacheComponents、export const instant、开发覆盖层中的即时导航验证警告,以及 cache-components-instant-false codemod。如果 next --version 显示版本低于 16.3,请先升级:

    • 运行 npx @next/codemod@latest upgrade latest,应用各版本间的 codemod。
    • 阅读相关的版本升级指南(例如 Version 16),了解 codemod 未涵盖的内容。
  • 不存在不兼容的配置键。 如果任何文件仍导出 dynamic、revalidate 或 fetchCache,cacheComponents: true 就会报错。运行 codemod 前,请先盘点这些导出,然后遵循迁移指南中针对各配置键的章节。每个值如何转换,以该指南为准。cache-components-instant-false codemod 不会移除这些配置。

  • experimental.dynamicIO 会导致致命错误。 该配置已重命名为顶层 cacheComponents,旧配置键会在构建开始前中止流程——请先移除它(或替换为 cacheComponents: true)。experimental.useCache 仍可作为已弃用的别名使用;设置 cacheComponents: true 后它就多余了,请移除以保持清晰。

备注
  • 启用标志前,基线无法通过。 如果应用已经使用 "use cache",启用标志前的构建会报错:please enable the feature flag cacheComponents。首先要做的就是启用该标志(采用 Incremental 方式时,在运行 codemod 前启用;采用 Direct 方式时,在修复路由前启用),而不是等到构建通过后再启用。请在开始时的摘要中注明这一点,以免看起来像是出现了回归。

  • 现有缓存可以保留。 按照迁移指南中的 fetch 和 unstable_cache 部分操作。不要仅为了启用 Cache Components 就重写这些缓存。

  • 离线文档。 指南链接在 node_modules/next/dist/docs/ 下有离线副本(从 Next.js 16.2 起随包提供),目录按编号排序(例如 node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md)。如果无法确定编号前缀,可以运行 find node_modules/next/dist/docs -name '<slug>.md' 来查找。/docs/messages/* 错误页面不会随包提供。

  • 未捆绑离线文档的旧版本。 开始前,建议用户运行 npx @next/codemod@latest agents-md:它会下载与版本匹配的副本到 .next-docs/,并在 AGENTS.md 中写入索引。它会修改用户仓库中的文件,因此请先征求用户同意,只有在用户愿意的情况下才运行。

工作流程

整个过程只有一个循环:从上到下遍历路由树,一次处理一个功能,并结合 next dev 和浏览器逐个采用各路由。构建是每项功能的最终检查,而不是日常工作界面。

第 1 步的选择是:先让所有路由退出验证,还是边修复边推进。无论选择哪种方式,循环都相同:

  • 采用安静的前置步骤(Incremental)。 运行 codemod,修复它无法处理的部分,并完整迁移之前需要静态渲染的路由。其他路由保留退出设置,留待后续 PR 处理。
  • 不采用前置步骤(Direct)。 启用 cacheComponents,并从构建首先标记的问题开始处理。循环相同,但在完成采用之前,所有修复都会留在同一个分支上。

两种方式的单路由成功标准相同:开发循环中没有错误,并且 next build 通过。每完成一项功能,都要与用户确认,并建议提交,但未经用户确认绝不提交。预计大部分时间会花在循环中,而不是前置步骤上。

背景

cacheComponents: true 要求每个路由都可预渲染。在 <Suspense> 之外读取请求时数据的路由会「阻塞」,并导致构建失败。export const instant = false 会标记路由允许阻塞,在开发和构建过程中都会解除阻塞;在布局上使用时,构建期间会覆盖整个子树,但客户端导航仍会单独验证每个后代分段。由 "use cache" 函数包裹的读取属于缓存边界,而不是阻塞性读取。

如果修复中引入了 "use cache",请遵循缓存指南,了解如何选择数据级或 UI 级边界并设置其生命周期,以及如何在变更后重新验证。如果结果会因参数或捕获的值而异,请参阅 use cache 缓存键参考。

通常会按以下顺序遇到三类阻塞项:

如果修复中引入了 <Suspense>,请遵循流式传输指南中的细粒度流式传输模式和防止 CLS 的指南。

  1. 请求时读取(cookies()、headers()、await params、await searchParams)。在页面或布局顶层等待这四项中的任何一项都会造成阻塞。params 和 searchParams 常被忽略,因为它们不像 cookies 和 headers 那样被视为「请求数据」。解决方法是将读取操作移至由 <Suspense> 包裹的子组件中;对于 params/searchParams,将 promise 传给子组件并在那里等待,不要在页面顶层使用 await。
  2. 模块/渲染时的同步 I/O(new Date()、Date.now()、Math.random()、crypto.randomUUID())。即使设置了 instant = false,这些操作仍会导致构建失败;此选项不会抑制它们。如果它们位于共享布局中,就会阻塞其下的所有路由。codemod 无法修复这些问题;运行后,请根据每条构建错误及其链接的文档,手动识别需要转换的报错调用(参见增量式预步骤)。
  3. 读取请求数据的 "use cache" 文件。 顶层带有 "use cache" 指令的文件不能导出 instant;将两者组合使用会导致 Only async functions are allowed to be exported in a "use cache" file. 错误,这表示该路由不应使用此指令。运行 codemod 前请将其移除。

工作范围

查找阻塞路由

工作期间优先使用 next dev,而不是 next build。

  • next dev —— 工作界面。访问某个路由;其阻塞错误会显示在开发覆盖层中,并提供完整堆栈跟踪和链接到各错误文档的修复卡片。一次处理一个路由——错误不会集中显示在同一处。路由本身仍会返回 HTTP 200,因此请查看覆盖层(或 .next-dev.log),而不是状态码。覆盖层中的错误已清除,是判定路由正常的一半条件;另一半是浏览器验证(参见步骤 2)以及该路由通过构建。
  • next build —— 仅用于检测。构建是对 next dev 的权威检查,而非替代品。在循环中,将其用作每项功能的最后一道关卡(通过构建是单个路由成功标准的一部分),并作为整个应用的最终验证。在 Incremental 中,构建还会确认前置步骤(codemod 已为每个路由启用 opt-out,且没有共享布局仍存在同步 IO 阻塞),然后你再提交该 PR。处理路由时不要用构建代替开发循环——编译通过并不能说明哪些内容最终进入了静态外壳,哪些内容进行了流式传输。默认情况下,构建会在遇到第一个阻塞路由时停止,因此也不适合估算工作量。迭代时有两个标志可用:--debug-build-paths 仅构建你指定的路由(以逗号分隔的 glob 文件路径模式,相对于项目根目录,例如 --debug-build-paths="app/admin/**/page.tsx"——不是 URL 路径;--debug-build-paths="app/(marketing)/about/page.tsx"——不是 /about;--debug-build-paths="app/admin" 不会匹配任何内容,并会静默构建 0 个路由),以及 --debug-prerender,它会禁用提前退出,使构建在首次预渲染失败后继续执行,报告所有阻塞路由,并打印更完整的堆栈跟踪,注明源文件和行号。

每个阻塞错误都有对应的文档页面——请打开查看。开发覆盖层和构建终端都会为每个错误打印一个 https://nextjs.org/docs/messages/<slug> 链接。该页面是修复问题的权威指南;行内消息只是摘要。每遇到一种不同的错误,都要获取其链接,即使你认为自己了解这种模式——指南会不断更新,而且同一类错误可能因路由读取的内容不同而有不同的正确修复方法。不要只根据行内消息自行尝试修复。(/docs/messages/* 页面不会打包在离线内容中;如果无法联网,请改为查阅 node_modules/next/dist/docs/ 下针对各 API 的指南,并在反馈时说明这一限制。)

在运行时验证每项修复

构建通过或覆盖层消失,并不能证明路由的实际行为正确——Cache Components 是一个运行时问题(静态外壳搭配流式传输的数据)。每次修复后都要验证,不要只在最后验证。

按优先顺序:

  1. 强烈推荐 next-dev-loop。 它会将 /_next/mcp 与实时浏览器通过 agent-browser 进行交叉核对,并在一次检查中发现编译和运行时问题。它提供的诊断信息(React 树、Suspense 边界、控制台和网络)比手动检查 next dev 更丰富。

    在开始循环前安装。不要等到遇到仅靠 next dev 无法解释的问题时才安装。此工具随本技能一同提供,因此请先检查它是否已可用,只有在尚不可用时才安装:

    npx skills add https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop
    

    技能文档会说明所需的 agent-browser 版本,并引导你完成相关操作。

    需要 Turbopack。 如果 package.json 的 dev 脚本传入了 --webpack,请告知用户并询问是否有理由继续使用 webpack。如果没有,请切换到 Turbopack(Next.js 16.3+ 的默认选项)。如果用户想继续使用 webpack,则跳过此次安装,改用仅构建循环。

    安装 next-dev-loop 本身无需获得许可。它是一种工具,就像安装开发依赖一样。如果用户在场,请简短告知他们你正在安装此工具以进行验证。在非交互式运行环境(CI、仪表板、沙箱)中,无需询问即可安装——“无法询问用户”不是跳过安装的理由。唯一合理的跳过原因是真正的技术阻碍:没有网络、没有 npm、文件系统只读、用户明确表示不允许添加新依赖,或开发脚本仅支持 webpack。如果跳过,请在最终报告中说明具体阻碍。

  2. 可自行操控的浏览器。 Playwright、直接使用 agent-browser,或任何浏览器自动化工具。仅在确实无法使用 next-dev-loop 时才使用。你将无法进行框架侧检查(/_next/mcp),因此仅靠 DOM 断言无法发现所有回归问题——在声称“已验证”时应更加谨慎。

  3. 仅构建验证。 如果完全无法运行开发服务器,构建就是唯一的验证信号。没有 <Suspense> 的 ○ (Static) 路由已通过构建得到完整验证(没有流式传输内容需要测试)。◐ (Partial Prerender) 路由仅验证了外壳——在汇报时请注明。

  4. 完全没有工具。 请用户运行开发服务器(或构建)并报告结果,或者交接已完成的阶段。

第 1 步:选择策略

询问用户想要哪些 PR,而不是工作的规模。与用户交流时,绝不要使用内部标签(Incremental、Direct)——这些只是你自己的辅助框架。要从 PR 和功能的角度提问,例如:“你希望我先开一个 PR,启用 Cache Components 并让每条路由都跳过验证,然后在后续 PR 中逐项处理实际的路由迁移吗?还是在一个分支上一次性完成所有工作?” 即使是很小的应用,增量路径仍然有价值(便于评审的 PR、可撤销,// TODO: Cache Components adoption 标记还可以作为下次会话的工作队列)。不要替用户做决定。

如果没有用户可询问,默认选择 Incremental 并记录这一选择。

遵循请求中的明确选择。如果用户要求增量迁移,同时也要求你完成迁移,那么先建立并验证增量检查点,再在同一任务中继续处理剩余路由。“完成”决定停止点,不会改变已选策略。

  • Incremental — 安静地完成前置步骤,然后进入循环。运行 codemod,让每个页面和布局都跳过验证,使构建通过,暂停并与用户确认(参见前置步骤结束时的确认),然后进入第 2 步的循环,将每项功能作为后续 PR 提交。
  • Direct — 跳过前置步骤。启用 cacheComponents,直接进入第 2 步的循环;构建中阻塞的路由就是工作队列。
incremental

运行 codemod 前,在 app 目录中搜索 ^export const (revalidate|dynamic|fetchCache),并按照迁移指南处理每个匹配项。将使用 dynamic = 'force-static' 或 dynamic = 'error' 的路由标记为需在此 PR 中完成完整迁移。codemod 不会移除不兼容的配置。

工作树有未提交更改时,codemod 会拒绝运行。请先提交或暂存无关的工作,或者传入 --force,让它的修改与当前 WIP 一起落地。常见的误报情况:如果你最近升级了 Next.js,package.json 和 lockfile 可能已有未提交更改——请先提交这些文件。

npx @next/codemod@latest cache-components-instant-false ./app

传入你在 requires 中确定的 app 目录。路径错误不会报错:它会报告 0 ok 并以 0 退出,因此请读取文件数量,并将数量为零视为运行失败,而不是已迁移的应用。

它会在该目录下的每个 {page,layout,default} 文件中插入 export const instant = false(附带 // TODO: Cache Components adoption 注释),跳过已声明 instant 的文件,以及标记为 "use client" 或 "use server" 的任何模块。然后设置 cacheComponents: true。TODO 注释就是循环中的工作队列。

如果无法使用 codemod(@next/codemod 版本较旧、环境受沙盒限制或处于离线状态),请手动复现其操作:对于 app 目录中每个不是 "use client" 或 "use server"、且未声明 instant 的 {page,layout,default}.{js,jsx,ts,tsx} 文件,在导入语句之后插入以下内容:

// 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

codemod 会让每个段都跳过验证,而不仅仅是根段,这是有意为之。配置自上而下解析,并以第一个显式配置为准:优先级最高的 instant = false 会决定整个子树。每个段都设置跳过验证后,移除某个段的设置只会验证该段;后代仍保留各自的跳过设置,因此构建仍能通过。如果只让根段跳过验证,移除该设置就会一次性重新启用整个应用的验证。

由于优先级最高的跳过设置会生效,请从上到下移除(先处理根布局,然后逐层向下)。只要祖先仍保留跳过设置,移除叶节点的设置就不会产生任何效果。

移除之前为静态的路由以及任何屏蔽其验证的共享段上的跳过设置。解决错误并确认这些路由仍会预渲染。仅有缓存的数据调用并不能证明路由仍是静态的。 接下来,运行 next build,找出 codemod 无法处理的阻塞项。构建结果才是验证依据,而不是运行 codemod;直接调用 new Date() / Math.random() 的共享布局仍然会失败,无论是否选择退出(参见背景)。如果常规构建报告同步 IO 错误,却没有指出调用位置,请使用 next build --debug-prerender --debug-build-paths="app/path/to/page.tsx" 重新构建该路由。对于它报告的每个同步 IO 错误:

  1. 模块/渲染时发生同步 IO。 使用构建输出中的路由、源文件和行号,以及 /docs/messages/ 链接来定位错误。如有需要,在整个代码库中搜索 new Date()、Date.now()、Math.random() 和 crypto.randomUUID()(不只是 app/**/layout.{js,jsx,ts,tsx}——读取操作可能位于布局导入的任何组件中)。不要修改未报告的匹配项。应用链接错误页面中的相应选项,然后在仅为解除构建阻塞而临时引入的边界上方添加这条注释:

    // TODO: Cache Components adoption. Added to unblock the build: remove this boundary to re-trigger the error and review the documented options.
    

    它与 codemod 写入的注释共享 TODO: Cache Components adoption 前缀,因此检查提交内容的 grep 会同时找到两者。移除边界会再次触发错误并显示修复卡片——这与循环中移除退出选项的过程相同。

每次修复后,如果可以,重新运行指定范围的构建,然后再次运行 next build,找出下一个阻塞项。重复此过程,直到常规构建通过。

构建通过后,确认每个延后渲染的路由仍然有退出选项,且没有共享退出选项覆盖之前静态渲染的路由。如果应用没有之前静态渲染的路由,且根布局仍然延后渲染,请确认它已添加退出选项(grep -n "export const instant" <app dir>/layout.*)。根布局会渲染所有路由,包括 /_not-found 之类的框架路由,因此如果遗漏了,请手动向其中添加 export const instant = false。

像 /_not-found 这样的合成路由没有用户文件——如果它们导致构建阻塞,应修复根布局的退出选项,而不是合成路由。客户端组件("use client")不能添加退出选项(从中导出 instant 会导致构建错误),但它们并非罕见的阻塞项。最常见的情况是根布局导航栏或页眉中的客户端组件调用 usePathname()/useSearchParams():它会通过 blocking-prerender-client-hook 阻塞_每个_动态路由,而静态路由会通过(因为预渲染时路径名已知),因此直到遇到动态片段时,这个问题才会显现。这不是祖先数据修复——请按照错误文档页面中的 <Suspense> 方案处理。只有当客户端路由因_服务端_数据而阻塞时,才在其祖先中修复该数据。

预备步骤结束:提交检查点

仅增量迁移。预备步骤完成后即可提交 PR。在开始第 2 步之前,记录构建通过的检查点。除非用户已要求你在同一任务中继续完成整个迁移,否则请停止并提交。用用户的语言与他们交流;不要说“增量”或其他内部标签,而要说明采用进展、PR,以及应用目前的状态。告诉他们:

  • 你做了什么:启用了 Cache Components、运行了 codemod、迁移了之前静态渲染的路由、修复了剩余阻塞项,并确认构建通过。
  • 有哪些变化:之前静态渲染的路由仍会预渲染。其他页面和布局保留了 // TODO: Cache Components adoption 退出选项。
  • 需要检查什么:之前静态渲染的路由仍会完全预渲染且可预取,并且延后渲染路由上的请求特定数据仍然是请求特定的。
  • 要问的问题:“要不要先把这部分作为独立 PR 提交,然后再逐个路由采用 Cache Components?还是继续在当前分支上进行?”等待用户回答。

如果用户已要求你完成迁移,则记录此检查点后继续,不要再次提出同一个问题。

直接模式

设置 cacheComponents: true,然后转到第 2 步。构建中阻塞的路由就是待处理队列。

第 2 步:内循环,一次移除一个功能的 opt-out

“功能”指单个产品界面,例如 app/settings/profile/**、app/posts/[slug]/**,而不是整个顶层应用,例如 app/dashboard/**。先端到端完成一个功能,再开始下一个。

在一个功能中,从上到下处理(先布局,再页面;先处理根布局)。先移除布局的 opt-out,再处理其后代,这样就能暴露布局自身的阻塞读取。(直接模式:无需移除 opt-out——修复每个失败的路由;如果祖先上的手写 opt-out 遮蔽了该路由,先移除它。)

处理中途构建通过,并不意味着布局已经干净。即使后代页面仍有自己的 opt-out,移除布局的 opt-out 也会让构建继续通过——每个页面都会遮蔽继承的验证。只有在下方没有任何内容遮蔽时,布局实际的阻塞读取才会显现。不要在布局边界就认定功能已完成。

除非浏览器确实无法访问,否则请使用配合浏览器的循环。关于何为“浏览器可用”以及如何安装浏览器,请以 next-dev-loop 技能为准。

循环:配合浏览器(首选)

每个路由:

  • 移除 opt-out(增量模式),或针对失败的路由进行处理(直接模式)。
  • 在开发环境中重新加载。覆盖层没有错误?跳到验证步骤。覆盖层仍显示红色?修复。
  • 修复——获取错误中链接的文档页面(https://nextjs.org/docs/messages/<slug>),并按照其中的方案操作。覆盖层中的内联文本只是摘要;文档页面才是准确信息来源。
  • 在浏览器中验证。确认首次绘制时可见的内容符合你在外壳中预期的结果——没有卡在回退内容上,也没有悄无声息地从空外壳中流式传输所有内容。
  • 如果修复改动了共享代码(例如布局、侧边栏组件),重新检查同级路由。对共享外壳的改动可能修好当前路由,却破坏同级路由。
循环:仅构建验证(备用方案)

仅在无法使用浏览器时采用——例如 CI、沙箱环境,或者用户没有运行 next dev 且你无法启动它。这种方式提供的信号较弱:只能确认构建通过且路由已预渲染,无法确认静态外壳和流式传输内容的实际情况。

每个路由:

  • 移除 opt-out(增量模式),或针对失败的路由进行处理(直接模式)。
  • 使用 --debug-build-paths app/<route>/**(仅构建该路由)或 --debug-prerender(完整构建,但在首次失败后继续)重新构建。路由通过了?继续处理下一个。仍然阻塞?修复。
  • 修复——获取错误中链接的文档页面(https://nextjs.org/docs/messages/<slug>),并按照其中的方案操作。
  • 如果修复改动了共享代码,重新检查同级路由。
  • 交接该功能时,将该路由标记为仅通过构建验证。每个 ◐ 路由仍需通过浏览器验证,功能才能算完成。
循环备注
  • 在原地修复时,往往会漏掉背景中提到的三类阻塞情况。缓存下游的请求(getThing(id))并不能解决页面主体顶部的 await params——应将参数 Promise 传入由 <Suspense> 包裹的子组件。
  • 含糊的调用需要向用户确认,而不是由 Agent 自行判断。如果不确定哪种修复合适、阻塞代码看起来涉及安全问题,或者用户可能有意让路由保持阻塞状态——请先阅读 references/per-page-decisions.md,再进行编辑。使用 next-dev-loop 的浏览器交接功能,在提问时展示该路由;截图可作为备用方案。看着页面时,“这里应该保持阻塞吗?”比对着文件路径更容易回答。
  • 不要用注释描述重构过程。Codemod(或你)唯一应留下的注释是用于选择退出的 // TODO: Cache Components adoption,以及用户已有的注释。不要在每个 <Suspense> 边界或 "use cache" 调用旁注释说明其作用——代码本身已经说明了。只有当代码无法清楚体现_原因_时才添加注释(例如,说明有意使用 Block 的理由)。
  • 对于许多采用相同机械式修复的路由,先验证一个有代表性的路由。然后使用相同方案批量处理互不重叠的路由组,并一并运行共享构建和浏览器检查。

为该功能的路由维护待办列表。当该功能的所有路由都处理完毕后,进入第 3 步。

步骤 3:验证功能

与用户确认前的检查清单:

  • next build 完成,且没有阻塞路由错误。
  • 功能中没有未说明的 TODO:grep -rn "TODO: Cache Components adoption" 能找到 codemod 添加的选择退出注释,以及前置步骤中解除阻塞的同步 I/O。任何残留的 instant = false 都是有意保留并记录说明的 Block——将注释改写为具体原因(参见 references/per-page-decisions.md → “何时应保留 Block”)。任何残留的 await io() 或 await connection() 都已经过审核,确认是有意保留,而非前置步骤留下的内容。
  • 在浏览器中访问每个路由:确认静态外壳先渲染,且每个 <Suspense> 后备内容都会替换为实际内容。如果可以,请记录这两种状态——后备内容(流式传输过程中)和最终渲染结果——以便向用户演示流式体验。如果流式传输太快,不便观察,请在浏览器中限制网络速度。
  • 填充任何可更新数据的新缓存后,执行变更检查,确认下一次读取能返回预期数据。
  • 如果运行时验证失败,请在采用前的分支上,或恢复其选择退出设置后,复现相同路由。若失败早已存在,那是环境或数据问题,而非采用新方案导致的回归。

然后与用户确认。规则与前置步骤相同:使用用户的语言。不要说“逐功能循环”或其他内部术语;要谈谈你采用了哪些功能,以及用户会看到什么。

  • 做了什么:说明涉及了哪些路由,以及每个路由对用户可见的效果(例如:“文章页面现在会在布局保持静态的同时,在骨架屏后流式呈现文章正文”)。
  • 有哪些变化:移除了选择退出设置,添加了后备内容,引入了缓存边界。
  • 展示胜于描述。按照 next-dev-loop 的浏览器交接流程,实时打开路由,让用户看到静态外壳 → 后备内容 → 最终内容的过程。如果无法现场演示,请附上已截取的前后对比截图。
  • 提供点击指南:用简短表格列出该功能涉及的路由、要打开的网址及观察要点(哪些内容会立即渲染、会出现哪些后备内容、哪些内容会流式传入),方便用户自行逐一验证。
  • 提出问题:“要把这个功能作为 PR 提交,然后继续下一个功能,还是就此停下?”等待对方回答。

**简单功能可以跳过确认环节。**如果采用某个功能只涉及移除其 // TODO: Cache Components adoption 选择退出设置(没有添加 <Suspense>、没有引入 'use cache'、也没有改变渲染顺序),用户不会看到任何变化。此时无需停下,直接继续下一个功能;下次确认时顺带提一句即可。

当所有功能都完成此流程后——每个残留的 instant = false 都位于说明原因的注释下方,且 grep -rln "TODO: Cache Components adoption" app 没有返回任何结果——如果用户想进一步提升体验,就引导他们阅读延伸阅读;否则就停下并交付。

路由表图标

ƒ → ◐ 是通常的采用结果。◐ (Partial Prerender) 表示静态外壳会进行预渲染,而请求时的内容会以流式方式传入——这是任何读取 cookies()、headers()、params 或 searchParams 的路由所追求的目标状态。当路由通过文档记录的逃生机制执行请求时工作时(例如,布局使用 await connection()),它确实可以保留为 ƒ;页面不再是_选择退出_,而是真正动态的。不要仅仅为了追求 ◐ 而移除逃生机制。反过来也是如此:instant = false 并不会强制路由变成 ƒ。图标反映的是路由在预渲染时的行为,而不是它导出了哪些验证选项。

◐ 表示外壳存在,但无法说明其中有什么。放置过高的 <Suspense> 边界(例如,包住整个页面主体,或在文章内容外使用 <Suspense fallback={null}>)会将可见内容从静态外壳移到流式传输的负载中;构建仍会报告 ◐,因为_有一部分_外壳完成了预渲染(通常只有带有框架标记的 <html><body>)。路由表无法告诉你外壳里有什么,但浏览器可以。如果外壳是空的,所有内容都以流式方式传输,请将 <Suspense> 边界下移,使其更靠近实际的动态读取操作。

延伸阅读

以下工作是可选的,相关内容位于文档中——将链接提供给用户,让他们决定接下来要做什么。不要在此技能中逐步讲解。

  • 扫描更多即时导航机会 — 采用完成后的可选后续步骤,绝非必需。构建通过并不代表一切都已完成,因为开发环境会在每次页面加载时验证所有路由(同时模拟页面加载和客户端导航),并能发现构建时的首次错误退出和后代路由屏蔽所遗漏的问题。对于不想采用 Partial Prefetching 的用户,可以将此作为实现即时导航的较小改动路径。采用 Partial Prefetching(见下文)也会执行同类循环,并能获得这些洞见,因此建议两者都做,由用户选择做哪一个或是否都做。参考文档就是要执行的循环。
  • next-partial-prefetching-adoption — 后续采用 Partial Prefetching 的技能:启用 partialPrefetching,并根据决策表审查每个 <Link prefetch={true}>(或在关闭该标志的情况下,借助 instant-link-prefetch-partial 洞见逐步采用)。它安排工作的方式与此技能安排 Cache Components 相同,但这些洞见仅在开发环境中提供,因此采用的是浏览器点击验证,而不是构建循环。建议在实现即时导航后采用,因为这些修复会直接影响每条路由的外壳可以预取多少内容。相关概念见采用 Partial Prefetching 指南。
  • 使用 e2e 测试防止回归 — @next/playwright 的 instant() 助手会断言导航时立即可用的 UI,因此回归问题会在 CI 中暴露。路由实现即时导航后建议添加此测试:next-dev-loop 确认它_现在_正常;instant() 测试则能确保它持续正常。
  • next-cache-components-optimizer — 一项独立技能,用于扩展每条路由的静态外壳,让更多页面内容得到预渲染,减少流式传输的内容。纯属优化,并非采用过程的一部分。

评论

支持 Markdown