首页
/ 为 Next.js 项目搭建 instant-navigation 测试基线:读懂 rig 六问与 `instant-nav.rig.md` 生成指南

为 Next.js 项目搭建 instant-navigation 测试基线:读懂 rig 六问与 `instant-nav.rig.md` 生成指南

2026-09-08 22:09:27作者:俞予舒Fleming

在 Next.js 的 Cache Components / PPR(Partial Prerendering)优化实践中,"让路由导航即时可见(instant navigation)"是可以通过自动化测试严格验证的,而验证的前提是为你的具体项目搭好一套 rig(运行基线)。rig-template.md 正是这一环节的脚手架:它先把"原理与环境无关、基建与项目强相关"这件事讲清楚,再给出一次性的勘探(discovery)流程、六个必答问题、两个派生字段、可直接套用的 instant-nav.rig.md 文件模板,以及三种真实场景的填写范例。读完本文,你将掌握如何为任意 Next.js 项目产出自己的 rig 文件,让后续每次 RED/GREEN 验证都跑在可信的生产构建上。

一次勘探,终身复用:rig 的定位与产出物

本仓库的 next-cache-components-optimizer 技能提出了一个清晰的分工:原则(invariant)是固定的,基建(rig)是你的。原则只要求"验证循环"必须存在且可信,但"怎么构建、怎么部署、怎么鉴权、怎么跑 Playwright、怎么循环"完全取决于你所在的技术栈。

  • 本地 next build && next start、CI/暂存环境的容器、每次 push 产生的 preview 部署,三者在原则层面等价
  • 结论永远来自"被测的构建产物",而不是来自"谁帮你跑了它"。

rig-template 文档把这件事落成一步到位的操作:对每个仓库只做一次勘探,把答案写进一个随仓库提交的 instant-nav.rig.md(放在仓库根目录,或放在 e2e 配置旁边),此后每次运行直接读该文件,不再重复勘探。技能里把这一步编排为 Phase 0(SETUP),在整个工作流(P 前置检查 → 0 勘探 → A 搭建 rig → B 基线 → C RED → D 修复 → E 对等性 → F 差分 → G 评审)中,SKILL.md 明确指出 rig 文件是"一次性搭建、每次优化可被构造性验证"的基础设施,而不是某一单个路由的产物。

勘探方法论:先查仓库,再问用户

rig-template 对勘探方式有明确主张:先检查,再提问(Inspect before asking)。绝大多数答案已经写在仓库里,按顺序排查以下五类来源即可:

  • package.json 的 scripts(重点找 buildstarttest:e2e);
  • e2e 配置(playwright.config.* 中的 baseURLwebServer、projects);
  • CI 配置(.github/workflows/vercel.json、GitLab/CircleCI 配置、Dockerfile);
  • next.config.*(是否已有 experimental 标志、当前的 cacheComponents / PPR 开启状态);
  • 现有 e2e 鉴权助手(在仓库中搜索 loginstorageStatesession 等关键词)。

只有仓库回答不了的问题才需要问用户,典型的是三类:哪个部署目标算"preview"、CI 中测试套件以哪个账号运行、以及 Agent 是否被允许在无人值守情况下推送并等待 CI。其余一切应以仓库证据为准。

六个必答问题 + 两个派生字段

rig 文件要求六问全部有答案,另外还有两个由勘探过程"喂"出来而非直接提问的派生字段:LIVENESS(由 LOOP 答案推导的、回显 SHA 的探针)与 WALLS(针对项目特有构建/运行障碍的积累清单)。

1. BUILD:生产构建如何产出与被服务

定义"被测的生产构建"来自哪里:按 push 触发的 preview 部署、暂存容器,还是裸的 next build && next start除了 next dev,任何一种都算——这正是 rig 的第一个硬性约束(参见 SKILL.md 的 Phase A):next dev 不做预取、其锁对阻塞路由不可靠,因此 dev 环境下的 instant() 结果不能当作有效的 RED 或 GREEN。

2. EXPOSE:何时打开测试 API,且永远不用于真实生产

experimental.exposeTestingApiInProductionBuild 是让生产构建内嵌测试 API 的开关,rig 必须为"每一个被测构建"打开它、并为"真实生产"永远保持关闭。常见写法有三种,rig 文件需记录本项目采用的那一种:

  • 显式环境变量:EXPOSE_TESTING_API=1(适合本地生产构建);
  • 通用 CI/暂存变量:process.env.DEPLOY_ENV === 'staging'
  • Vercel 平台条件:process.env.VERCEL_ENV === 'preview'

关键是条件必须在 next build 时成立,而不是只在 next start 时成立——否则 instant() 可能无法在测试超时前拿到测试 cookie,先重建产物再调试断言,而不是去调试断言本身。

这一开关并非 rig-template 的发明。在 Next.js 源码中,packages/next/src/server/config-shared.ts 给出了它的官方注释:该标志将 "Instant Navigation Testing API" 暴露到生产构建(该 API 在开发模式下始终可用),使 e2e 测试能够控制导航时序、在动态数据流入前对预取/缓存 UI 做确定性断言,并明确警告"该标志仅供性能剖析与测试使用,切勿在面向用户的生产部署中启用"。这就是文档反复强调 EXPOSE 条件必须与真实生产互斥的底层依据。

3. RUN:Playwright 套件如何被调用、针对哪个 BASE_URL

记录 e2e 的实际执行命令与目标地址来源:是 BASE_URL=http://localhost:3000 playwright test 还是 CI job 中的其他拼写。BUILD、EXPOSE、RUN 三问共同回答"在哪个产物上、以什么条件、跑哪条命令"。

4. TEST USER:套件以哪个账号运行、登录如何发生

套件(在 CI 中即 CI 账号,本地即 e2e 登录 fixture 所代表的账号)以何种方式鉴权——helper、storageState 还是 API token?该账号持有哪些 flags / plan / role / data?答案直接喂给 Phase B 的"以测试用户身份确认 marker 真实可达"与 C-gate 的可信度判断。

5. DRIFT:作者会话与测试用户环境之间的一切差异

把可能造成差异的每一项都枚举出来:功能开关、套餐与授权(entitlements)、角色、种子数据与空数据、locale、A/B 分桶。每一项都是 RED 可能变得不可信(untrustworthy)的通道,这份清单是 C-gate(参考 reference/red-test-robustness.md)的输入:作者自己的登录会话里能看到 marker,不代表测试账号能看到。SKILL.md Phase B 也点名了这一点——套件以测试账号运行,而不是以作者会话运行,环境漂移是产生不可信 RED 的常见根源。

6. LOOP:无人值守的迭代闭环

给出你 rig 的闭环:推送 → 构建 → 对产物跑 e2e → 读失败 → 修复 → 再推送(CI 路径);或构建 → 启动 → e2e(本地路径)。同时记录 Agent 无法独立完成的事项(部署审批、密钥、受保护分支)。LOOP 还要求包含活性探针(liveness probe)

  • 找到能回显被部署提交 SHA 的端点或响应头(例如 /healthz 路由或 x-deployed-sha 头),让 CI 在相信某个判定前先确认"被测构建 == HEAD";
  • 若平台不暴露任何回显 SHA 的端点/响应头,就自己加一个:把构建期提交变量(VERCEL_GIT_COMMIT_SHA、CI commit 变量)挂到 /healthz 或某个响应头上;或退而轮询部署平台 API,找出 commitSha === HEAD 的那次部署;
  • 记录所选机制。

本地 build && start rig 无需 SHA 探针——被测产物就是刚构建出来的那个。但本地闭环仍有三个硬性注意事项:记录端口、启动前停掉上一次的服务、端口占用时让闭环失败(EADDRINUSE);测试前确认监听该端口的是新启动的进程。由于 next start 会 fork 出 next-server 子进程,启动器进程 ID 不一定持有端口,因此要么把服务放进一个能被 rig 整体停掉的进程组,要么在下一次构建前发现并停掉占用记录端口的进程。

产出物:instant-nav.rig.md 文件模板

勘探完成后,复制以下模板、逐字段填写、随仓库提交:

# instant-nav rig: <project>

- BUILD: <command / platform that produces the measured production build>
- EXPOSE: <the condition wired to exposeTestingApiInProductionBuild>
- RUN: <e2e command> against <how BASE_URL is obtained>
- TEST USER: <account> via <login mechanism>; flags/plan/role/data: <...>
- DRIFT: <the enumerated drift surface>
- LOOP: <pushCIe2e, or local buildstarttest>; agent limits: <...>
- LIVENESS: <endpoint/header echoing the deployed SHA; n/a for local build && start>
- WALLS: <project-specific build/run obstacles + their workarounds>

WALLS:撞过的墙,都要记录下来

真实应用几乎不可能第一次就为生产顺利构建:缺密钥、导致预渲染失败的 server-only 导入、被反复重启的服务占用的端口……rig-template 明确要求第一次撞上某堵墙时就记下这堵墙及其绕过方式。WALLS 是其他字段无法捕获的、项目特有的构建/运行障碍的积累地,也是把"勘探"从一次性动作变成可持续资产的关键。

三种真实 rig 的填写范例

rig-template 给了三个可直接对照的填好的例子,分别覆盖无 CI、通用 CI+容器、Vercel preview 三种主流形态。

无 CI / 纯本地

  • BUILD:EXPOSE_TESTING_API=1 next build && next start
  • EXPOSE:即该环境变量
  • RUN:BASE_URL=http://localhost:3000 playwright test
  • LOOP:单机完成 构建 → 启动 → 测试;全程可被 Agent 独立驱动——无需推送、无密钥、无部署等待。

通用 CI + 容器

  • BUILD:流水线构建镜像并部署到暂存 namespace
  • EXPOSE:process.env.DEPLOY_ENV === 'staging'
  • RUN:CI job 对暂存 URL 运行 Playwright
  • LOOP:推送 → 流水线 → e2e;流水线就绪后即可全程无人值守。

Vercel preview 部署

  • BUILD:每次 push 构建一个 preview
  • EXPOSE:process.env.VERCEL_ENV === 'preview'
  • RUN:playwright testBASE_URL=<preview URL>
  • LOOP:推送 → preview → e2e;preview 部署与 VERCEL_ENV 门控就绪后即可全程无人值守。

对照你的项目选一条主线即可,同时注意 rig 文档的态度:平台、环境变量拼写、命令都只是"待翻译的示例"而非"必须遵守的要求"——把原理翻译成你仓库的真实形态,而不是反过来改造仓库去迎合示例。

为什么 rig 必须在"锁"上跑:从机制到验证闭环

理解 rig 的每个字段,最终都要回到 @next/playwrightinstant() 机制上,否则容易在 EXPOSE、LIVENESS 等字段上踩空。在 packages/next-playwright/src/index.tsinstant() 实现中可以看到完整协议:它通过浏览器上下文 addCookies() 写入名为 next-instant-navigation-testing 的 cookie(值形如 [0, p<random>],见同文件第 30 行常量定义),cookie 触发构建内的 CookieStore change 事件从而获取内存中的导航锁;在锁内,Next.js 只提供缓存/静态外壳数据,动态数据被门控;回调完成后按过去过期时间(expires: 1)重写 cookie 来释放锁。

由此可以推导出 rig 的几条硬约束,它们恰好对应 rig-template 反复强调的要点:

  1. 空转通过(vacuous pass)是最危险的失败模式。如果构建产物没有内嵌测试 API(即 EXPOSE 没配好),cookie 会被忽略,instant() 不抛错、导航正常执行,测试"静默通过"。正如 reference/red-test-robustness.md 所述,绿色 instant() 测试只有在锁确实生效时才有意义,而验证锁生效的最直接证据就是 Phase C 的 RED 本身。
  2. 基线的先决条件来自 EXPOSE 而不是 RUN 阶段:锁必须在 next build 期间就嵌入产物。文档明确提醒:instant() 获取测试 cookie 失败导致超时,通常是 EXPOSE 条件只在 next start 时成立、构建时未内嵌 API 所致。
  3. 远端 rig 必须先验活再信判定:对任何部署/远端构建,先轮询 LIVENESS 探针确认产物包含 HEAD,否则过期部署会读出假的 RED 或 GREEN。本地 next build && next start 无需此步。

把这些机制与 test-template.md 中"软导航用真实 <Link> 点击、硬导航用 page.goto() + baseURL"的测试模板对照,可以串出整条链:rig(本模板产出)保证"在哪个可信构建上测";测试模板保证"测什么断言";reference/red-test-robustness.md 保证"RED 为何红、是否可信"。三者共同构成可构造性验证:最大化静态外壳的价值取决于能否证明它,而证明方式就是 rig 支撑下的自动化检查。

小结:rig 是交付物,不是一次性杂务

如果你只记得一句话,那就是:rig-template 教你把"验证循环"固化成一个随仓库提交的 instant-nav.rig.md,让每一次"该路由是否 instant"的判定都有同一个可信起点。六问(BUILD / EXPOSE / RUN / TEST USER / DRIFT / LOOP)把项目基建翻译成可重复的答案,LIVENESS 与 WALLS 把"部署对没对上 HEAD""踩过哪些坑"沉淀成资产,三个填充范例则给了从纯本地到 CI 到 Vercel preview 的起点模板。从 packages/next/src/server/config-shared.ts 的配置开关到 packages/next-playwright/src/index.ts 的锁协议,仓库源码完整支撑了模板里每一条看似武断的规则——它们不是风格偏好,而是让验证不空转、让绿色有意义的前提。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
521
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
392