mcpField
N
next-cache-components-optimizer

next-cache-components-optimizer

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

关于 next-cache-components-optimizer

用于 Next.js 应用的 Agent 技能,通过生产构建中的 Playwright 测试优化路由的即时导航外壳。它以自动化回归测试验证静态界面先呈现、请求时内容随后流入。

主要功能

  1. 01以 instant() 编码导航目标
  2. 02验证静态外壳与延迟内容
  3. 03区分硬导航和客户端导航
  4. 04建立生产构建测试流程

适用场景

  1. 01让路由外壳在数据加载前呈现
  2. 02为导航优化添加回归保护

安装

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

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

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

rig-template.md
test-template.md
reference/
red-test-robustness.md

详细介绍

next-cache-components-optimizer

搭建一个 Agent 优化循环,让 Next.js 路由从“并非即时”变为“即时”,并保持这一状态。该循环由测试驱动:将目标编写为一个失败的 @next/playwright instant() 测试,逐步修复直到通过,并将测试作为回归保护。针对每个目标路由和入口点各运行一次。按顺序执行 P → G 阶段;每个阶段都以一道门槛结束。

修改路由之前,请先阅读随附的 优化静态外壳 指南,路径为 node_modules/next/dist/docs/01-app/02-guides/optimizing-the-static-shell.md。 如果无法使用该文件,请查看在线指南。指南负责说明框架行为和实现模式。本技能负责导航契约、生产环境测试装置、可靠的 RED 到 GREEN 循环、对等检查、差异分析和报告。

哪些内容是固定的,哪些由你决定

这里有一项内容是固定的,其他内容由你决定。在将下方任何命令、平台或环境变量视为要求之前,请先阅读本节。

  • 不变项:验证循环。 如果无法证明静态外壳已最大化,那么这样做就毫无价值。证明来自自动化检查:在动态数据受到门控的锁定状态下,静态外壳仍会提交。RED 表明差距所在,GREEN 表明差距已弥合,测试将作为回归保护一同发布。该检查必须在类似生产环境的构建上运行,并且不能在未实际验证的情况下通过。只需搭建一次循环;之后的每项优化都能通过构造得到验证。交付物是这个循环,而不是某个特定路由。
  • 实现机制:@next/playwright instant()。 本技能将 instant() 用作标尺,而非秒表(阶段 A)。它来自 @next/playwright(与 @playwright/test 一同安装,版本线与 next 相同),因此不绑定任何宿主环境。请保留它。手动测量导航耗时过于不稳定,无法作为可靠依据;本技能正是为了避免这种失败模式。
  • 由你决定:测试装置。 如何构建、部署、验证身份、配置 Playwright 和运行循环,都取决于你的技术栈,而非本技能。本地运行 next build && next start、CI/预发布环境容器,以及每次推送后部署预览环境,都是同样有效的测试装置;判定结果取决于构建,而非平台。阶段 0 会将不变项映射到你的代码库中。请将下文提到的平台名称、环境变量写法和命令都视为需要转换的示例,而非要求。

两种入口,两种即时 UI 契约

用户可以通过两种方式访问路由:

  • 初始加载(硬导航) 会提交路由预渲染的静态外壳;延迟部分则在其加载骨架后方流式传输(Suspense 回退 UI、loading.tsx)。
  • 客户端导航(软导航) 会复用共享布局,并为目标路由中发生变化的片段提交可用的 App Shell。

相同的实现模式可以让任一种入口即时呈现。测试的差异仅在于如何驱动导航(见下文“在测试中驱动导航”);由于软导航会复用源路由与目标路由分歧点上方的布局,可用 UI 也可能不同。请保护用户指定的入口点;如果两种入口都重要,则两者都要保护。参见“即时”的含义。

因此,本技能不只用于增大直接加载时的静态外壳。如果客户端导航并非即时,也可以通过缓存可复用的工作,或将请求时工作移到专门的边界后方,修复问题,让目标路由可用的 App Shell 立即提交。如果该 App Shell 已经能够即时呈现,而目标是提前获取更多特定于 URL 的内容,请转交给 next-partial-prefetching-optimizer。

目标

立即提交最有意义的可用 App Shell UI 改动,并让之后只有真正需要在请求时执行的工作继续流式加载。已提交的测试会以确定性方式编码 present ∧ instant。工作流通过判断额外要求非空白:仅通过 instant() 检查,空壳也会被视为满足条件。

instant() 是一把尺子,不是秒表:断言外壳会在锁定条件下显示;不要计时。要获得可信的结论,必须进行生产构建(阶段 A)。

锁定条件下的 GREEN 是确定性结论;每个门禁都能让它保持可信。

向用户汇报

这个循环旨在无人值守地运行,因此不会在步骤之间停下来提问。处理用户指定的导航,完成后停止。重要的是如何措辞和呈现结果,而不是打断用户的次数。以下机制——rig、RED、GREEN、门禁——都是你的支架;用户无需听到这些词。

  • 使用用户的语言。 用用户能看到的内容描述问题和结果:“导航到仪表盘时,必须等图表查询完成后才有任何内容绘制出来;现在布局和骨架屏会立即绘制,图表则逐步加载”——不要提 RED/GREEN、锁定条件或阶段字母。
  • 展示胜于描述。 汇报路由时,驱动浏览器(或附上前后截图),让用户看到外壳立即呈现、数据逐步加载,而不是只读到你的说法。前后完全相同,就说明修复没有效果——回滚。
  • 将一次运行呈现为用户可以逐项点击查看的结果列表——每次导航一行:路由、立即呈现的内容,以及逐步加载的内容——不要提供循环过程的逐字记录。
  • 只有遇到真正的分岔时才提出问题: 会改变行为的修复、涉及安全的读取,或按设计动态变化的路由(适合逐链接预取,而非扩展外壳)。干净且即时的修复不构成分岔——继续处理。无人可问(无人值守运行)时,不要阻塞:采用安全的默认方案并说明假设——对于缓存新鲜度的选择,将读取放在 <Suspense> 后面(始终获取最新数据,仍然即时),而不是猜测 cacheLife。

工作流

- [ ] P  PREREQS      Next.js 16.3+ with cacheComponents: true; upgrade first → below
- [ ] 0  SETUP        once per repo: discover + write instant-nav.rig.md     → rig-template.md
- [ ] A  RIG          production build with the testing API exposed          → below
- [ ] B  BASELINE     unlocked: the marker renders for the test user         → test-template.md
- [ ] C  RED          locked instant(): the shell does not commit            → test-template.md
- [ ] C-gate          VERIFY-RED: stop until the RED is trustworthy          → reference/red-test-robustness.md
- [ ] D  FIX          apply the matching documented pattern                 → guide links below
- [ ]      D1 reuse existing loading UI; keep the shell meaningful
- [ ]      D2 preserve layout and responsive behavior
- [ ] E  PARITY       the refactor changed only whether the route is instant
- [ ] F  DIFFERENTIAL revert only the fix → RED; re-apply → GREEN            → reference/red-test-robustness.md
- [ ] G  REVIEW       PR checklist (below)

阶段 B 和 C 用于构建测试;只有 C 阶段的锁定测试会随项目发布。


P. 前提条件:启用了 Cache Components 的当前 Next.js

该工作流依赖当前 Next.js 所提供的框架能力:

  • Next.js 16.3+,并在 next.config.ts 中设置 cacheComponents: true。如果没有 Cache Components,就没有可优化的静态外壳。
  • @next/playwright 与项目的 next 属于同一发布系列;它提供 instant()。通过 npm ls next @next/playwright(或项目的包管理器)核实版本,并在不一致时统一。匹配的测试 API 位于 next 运行时中,由配置标记 experimental.exposeTestingApiInProductionBuild 启用(阶段 A)。

如果项目不满足这些条件,请先升级(npx @next/codemod upgrade 可自动完成大部分工作),然后在 next.config.ts 中启用 Cache Components:

export default { cacheComponents: true }

启用此标记会显示需要优先处理的阻塞路由;next-cache-components-adoption 技能可引导完成迁移。等应用在 Cache Components 下构建成功后,再使用此优化器。

这一门禁是有意设置的:此技能面向当前 Next.js,以下结论在旧版本上都没有意义。

0. 设置:每个仓库只需发现一次项目的运行环境

本技能中的原则是固定的;它们所依赖的基础设施由你决定。首次在某个仓库中使用时,发现项目的构建、部署、身份验证和测试方式(先检查仓库,只询问用户无法从中获知的信息),然后将答案写入已提交的 instant-nav.rig.md。之后每次运行都读取此文件,而不再重新发现。所需的构建、测试上下文、导航约定、迭代循环和文件模板均见 rig-template.md。

如果仓库尚无 Playwright e2e 测试框架,则搭建一个最精简的框架(@next/playwright、包含 baseURL 的配置,以及一条已通过身份验证的路径)也是此步骤的一部分;迭代循环不假设已有测试套件。

A. 运行环境:公开测试 API 的生产构建

搭建 instant-nav.rig.md 中描述的运行环境。在所有平台上都必须满足两项不变条件:

  1. 绝不在 next dev 上测量。 它不会预取,而且它的锁无法可靠地阻塞路由,因此 dev 环境下的 instant() 结果不能作为有效的 RED 或 GREEN。

  2. 被测构建必须公开测试 API。 否则 instant() 会静默地不执行任何操作,测试也会在没有实际验证的情况下通过(参见 reference/red-test-robustness.md)。锁已生效的证明就是 C 阶段本身的 RED:未修复的目标路由是已知的阻塞路由,而它在锁下出现 RED,说明此构建上的锁确实生效(C 门槛);test-template.md 中的自验证变体则提供带内保证。将 experimental.exposeTestingApiInProductionBuild 绑定到一个条件,使其对每个被测构建都为 true,而在生产环境中绝不为 true:

    experimental: {
      // Use the condition your platform provides, and record it in the rig file:
      //   local:       an explicit opt-in, as below
      //   generic CI:  process.env.DEPLOY_ENV === 'staging'
      //   Vercel:      process.env.VERCEL_ENV === 'preview'
      exposeTestingApiInProductionBuild:
        process.env.EXPOSE_TESTING_API === '1',
    }
    

任何公开测试 API 的类生产构建都可以作为运行环境:本地执行 next build && next start、CI/预发布容器以及预览部署都同样有效;结论取决于构建,而非平台。设置要求见 rig-template.md。

对于任何已部署或远程构建,在信任测试结论前,轮询运行环境的 LIVENESS 探针,确认产物包含 HEAD(过期部署会导致误判为 RED 或 GREEN);本地执行 next build && next start 则无需此步骤。探针机制见 rig-template.md。

B. 基线(未加锁):开发脚手架,不要随 PR 提交

在不使用 instant() 锁的情况下执行真实导航,并断言目标页面的 SHELL_MARKER 会以测试用户身份渲染:即 e2e 测试套件通过身份验证的账号(CI 中为 CI 账号;本地为你的 e2e 登录夹具),并使用该账号的标志、套餐、角色和数据。这可以确认标记确实存在且可访问:没有受标志控制、没有被重定向,也不是猜测出来的选择器。测试套件使用的是测试账号,而不是作者的会话;这种环境漂移(运行环境 DRIFT 列表)是导致 RED 不可信的常见原因。脚手架和运行命令:test-template.md。 在提交 PR 前删除此基线。

C. RED(锁定)+ VERIFY-RED 门禁

用 instant() 包裹相同的导航;断言在锁定期间页面外壳已提交。这里的 RED 就是差距。这是要随项目发布的测试 (test-template.md)。

断言完整的预期契约:有意义的页面外壳标记和现有加载状态可见,锁定期间不存在请求时 UI,释放后渲染完成的 UI。不要为了让测试更容易而更改现有数据源、生产选择器或必需的路由变体。不得跳过或弱化任何必需的契约。

如果路由具有延迟内容,优先使用自验证变体。如果路由在阻塞期间无法构建,或 cookie/session 读取仍然是 GREEN,请使用 reference/red-test-robustness.md 中的 RED 方案。

C 门禁:在确认 RED 可信之前,不要开始优化。 如果 RED 变红的原因不对,你优化的路由可能根本没有问题。

最终要确认的问题是:作为测试用户,SHELL_MARKER 在未锁定时是否会渲染? 以测试用户身份重新运行阶段 B 来回答,不要通过给随项目发布的测试添加断言来回答。两分支的解决方法(否 → 标记或环境问题;是 → 确实存在差距,继续到 D)、不可信 RED 的完整分类、检查清单和示例都在 reference/red-test-robustness.md 中。现在就阅读。

D. 修复:应用文档中的模式

使用你在开头阅读的 优化静态外壳 指南。根据阻碍问题选择相应章节,不要在此重复指南中的框架说明:

阻碍问题指南模式
稳定的 UI 被更广泛的加载状态隐藏将静态 UI 保留在外壳中
布局或页面在树中的过高位置等待请求时数据将数据访问下移
新增或移动的边界需要实用、稳定的回退内容设计加载状态
可在多个请求间安全复用结果缓存可复用的工作

指南中的示例涵盖了常见的实现形式:

保留路由现有的数据源、新鲜度、授权和已完成的行为。不要将可变读取替换为构建时导入,只为让它看起来是静态的。如果现有读取可以复用,就缓存它;如果每个请求都必须计算,则对其进行流式传输。

如果开发或构建过程中出现其他即时导航 Insight,请遵循重构时进行验证,然后处理它指出的规范 Insight。对于元数据、视口和非确定性值等特定 API 的阻碍问题,这一点尤其重要。

复用现有加载 UI,并确保生成的外壳在所有受支持的断点下都有意义。只有当解析后的组件也没有视觉占用空间时,空回退内容才是有效的。

如果优化新增或扩展了缓存边界,请遵循重新验证。如果写入操作可能更改缓存数据,则填充缓存、执行变更,并验证下一次读取会返回更新后的值。instant() 证明的是就绪状态,而非变更后的数据新鲜度。

不要使用 export const instant = false、弱化契约,或将空文档外壳作为优化方案发布。

D 阶段门槛:只有在生产构建测试机上、处于锁定状态时,C 阶段的锁定测试通过 GREEN,D 阶段才算完成,代码编译通过并不代表完成。这个 GREEN 是修复循环的确定性停止点;接下来进入 E 阶段。

如果唯一缺失的即时 UI 是 URL 特定内容,请停在在即时 UI 中包含 URL 特定内容,并移交给 next-partial-prefetching-optimizer。

E. 一致性:重构只改变了路由是否即时呈现

这项下移是机械式转换,而非重新设计。完成后,路由必须呈现与之前相同的树、数据、顺序、空状态和错误状态、重定向及交互;唯一可观察到的差异是外壳现在会即时提交。请验证:

  • 渲染输出相同。 移动后的 await 会计算并返回相同的值;流式传输完成后,对于测试用户,路由显示的内容应与基准分支相同。
  • 请求输入仍然有效。 测试路由或 rig 中提到的身份验证、Cookie、参数、搜索参数和数据变体。
  • 副作用仍会触发。 延迟执行的 redirect() 或 notFound() 仍会在请求时发生。确认未授权和记录缺失时的行为。如果路由必须保留 HTTP 状态,请考虑流式传输的状态码行为。
  • 受支持的视口在流式传输后能显示真实 UI。
  • 客户端状态得以保留。 布局 UI 会提升到稳定的外壳中,而不是在解析完成后替换,因此打开的菜单、滚动位置、焦点和输入状态都会在流式传输期间保留。请参阅保留 UI 状态。
  • 将原有故障单独处理。 如果路由在更改后出错,请在基准分支上复现。如果那里也出现相同故障,那就是环境或数据问题,而不是优化器回归。

如果路由是否即时呈现以外的任何内容发生了变化,请缩小重构范围。

F. 差异验证

只撤销修复 → RED;重新应用 → GREEN;关联两次运行的链接(reference/red-test-robustness.md)。每项旨在区分此优化的契约,在撤销后都必须为 RED,在重新应用后都必须为 GREEN。在已部署的 rig 上,先确认每次运行都处于活动状态(LIVENESS,阶段 A),再相信其颜色。

最终重新应用后,请从 rig 运行完整的范围内命令。经过筛选的子集、书面列出的命令,或跳过必需测试的测试套件,都不算最终 GREEN。

G. 审查(PR 检查清单)

如果 RED 从未可信,那么最终状态为 GREEN 就毫无意义。测试可信度项目列于稳健性检查清单(reference/red-test-robustness.md);确认这些项目后,再检查以下 PR 专属项目:

  • 已展示差异:没有修复时为 RED,有修复时为 GREEN,且已关联运行记录。
  • 已确认一致性(E):内容、重定向和状态相同。
  • 适用时已验证变更:填充任何数据可更新的缓存后,通过变更测试确认下一次读取返回预期数据。
  • 已保留时效性:新的缓存作用域遵循数据现有或明确选定的生命周期。
  • 已复用现有加载 UI(D1):没有新增仿照页面的骨架屏。
  • 外壳在受支持的视口下与真实渲染一致(D2)。
  • 已移除基线:只保留 C 中锁定的测试。

整个工作流的停止条件: C 中锁定的测试在 rig 上为 GREEN,差异验证(F)成立,并且以上每一项都已勾选。在这三项全部满足之前,工作尚未完成。

在测试中驱动导航

  • 软导航 → 驱动真实的 <Link> 点击。初始加载 → 在 instant() 内使用 page.goto(),并设置 baseURL 选项。不要用 goto 代替软导航的判定;两种契约可能不同(test-template.md)。
  • 使用并行路由时,软导航只会重新渲染发生变化的插槽;客户端渲染的导航 UI 完全不会重新渲染。不要追查导航根本不会触及的插槽。请参阅使用并行路由实现加载和错误 UI。

文件

  • rig-template.md:阶段 0 的生产构建、测试上下文、导航 契约和无人值守循环发现。
  • test-template.md:已交付的两种导航类型的 instant() 规范(阶段 C),以及 删除后再提 PR 的基线脚手架(阶段 B)。
  • reference/red-test-robustness.md:C 门槛和阶段 F。对不可信 RED 测试的分类、 检查清单、差分方案、空洞通过的失败模式以及案例演练。

优化之后

目标路由达到即时响应后,检查应用是否已采用部分预取(Partial Prefetching)(配置中有 partialPrefetching: true,或者增量推出期间相关目标仍使用 prefetch = 'partial')。

请通过机械方式进行检查:

rg -n "partialPrefetching|prefetch\s*=\s*['\"]partial['\"]" --glob 'next.config.*' --glob 'app/**' --glob 'src/app/**'

如果配置中有 partialPrefetching: true,则表示应用已全局采用。如果只有 prefetch = 'partial' 匹配,则将这些目标路由段视为已在增量推出期间采用,并继续检查其他目标路由。

  • 已采用: 如果所请求的即时 UI 针对特定 URL,且目标应用外壳已即时响应,请继续使用 next-partial-prefetching-optimizer。它负责决定每个链接的取值和成本。
  • 尚未采用: 推荐使用 next-partial-prefetching-adoption。 该技能会让应用采用更优的预取模型:默认预取共享应用外壳、减少对可见链接重复发起的完整预取请求、审查现有 <Link prefetch={true}> 用法,以及仅在 URL 特定内容值得额外服务器开销时选择性地进行每链接预取。

评论

支持 Markdown