关于 next-cache-components-optimizer
用于 Next.js 应用的 Agent 技能,通过生产构建中的 Playwright 测试优化路由的即时导航外壳。它以自动化回归测试验证静态界面先呈现、请求时内容随后流入。
主要功能
- 01以 instant() 编码导航目标
- 02验证静态外壳与延迟内容
- 03区分硬导航和客户端导航
- 04建立生产构建测试流程
适用场景
- 01让路由外壳在数据加载前呈现
- 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.mdtest-template.mdreference/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/playwrightinstant()。 本技能将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 中描述的运行环境。在所有平台上都必须满足两项不变条件:
-
绝不在
next dev上测量。 它不会预取,而且它的锁无法可靠地阻塞路由,因此 dev 环境下的instant()结果不能作为有效的 RED 或 GREEN。 -
被测构建必须公开测试 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 特定内容值得额外服务器开销时选择性地进行每链接预取。