Next.js Insight Error Page 编写与审计实战指南:从 FixCard 框架卡片到规范化文档结构
本文基于 Next.js 仓库内置的 Agent 技能 SKILL.md,系统讲解 errors/<slug>.mdx 中 kind: insight 类型错误页(Insight Error Page)的编写与审计方法。读完后你能掌握:如何用「来源链(Source of truth chain)」定位框架卡片数据与错误标题的源头、如何严格按规范模板产出与 dev overlay 修复卡片一一对应的文档页,以及如何用完整的审计清单逐项核对存量页面。
一、什么是 Insight Error Page
Next.js 的 Instant Navigations 功能会在开发期与构建期对「即时导航」做校验。当校验发现问题时,dev overlay 会展示一条错误标题(headline)和一组修复卡片(FixCard),而 errors/ 目录下的 MDX 页面就是这些错误的文档化落地:它们随仓库发布到 nextjs.org,与 dev overlay 中的卡片集保持镜像关系。
当前仓库中共有 16 个页面带有 kind: insight frontmatter,集中在 blocking-prerender-*、instant-* 两类前缀下,例如 blocking-prerender-random.mdx、instant-shell-url-data.mdx、blocking-route.mdx 等。
该技能覆盖两种工作模式:
- 编写模式:「为
next-prerender-random创建错误页面」「编写同步 IO 文档」; - 审计模式:「审计 blocking-prerender-dynamic 页面」「检查错误页面是否与框架一致」;
- 以及一切涉及
errors/*.mdxinsight 页面的任务。
一个关键的术语约定:frontmatter 中写 kind: insight,但正文一律称其为「errors」,绝不写「insights」。应写 "this error"、"error pages"、"dismiss the error"。
二、来源链:每个决定都要能追溯到源头
技能文件给出的核心方法是「来源链」——遇到任何不确定时去读源头,不要猜:
| 决定内容 | 来源文件 | 读取方式 |
|---|---|---|
| 卡片标题、ID、分组、代码片段、链接 URL | instant-guidance-data.ts | 每个 FixCard[] 数组就是一个错误族 |
| 错误标题(用户看到的字面文本) | sync-io-messages.ts、blocking-route-messages.ts 等 | createSyncIOError、createDynamicBodyError 等工厂函数,模板字符串即标题 |
| 既有页面(需要保留的内容) | 本仓库的 errors/<slug>.mdx |
通读全文;无法放入修复卡片的内容迁移到 Gotchas 或 Other options |
| 规范 API 文档(术语) | 本仓库 docs/01-app/ | 所有 API 名、指令名、概念都与发布版文档交叉核对 |
| 页面结构模板 | 技能文件本身 | 页面的规范形态 |
| Vercel 写作风格 | vercel/front 中的 vercel-technical-writing 技能(本仓库不含) |
全量应用;技能中的「Voice and style」是最小化规则 |
框架卡片数据:FixCard[] 就是错误族
instant-guidance-data.ts 定义了 dev overlay 卡片的全部元数据。从源码可以看到卡片类型与分组体系(约 L31–L59):
export type FixCardGroup =
| 'stream' | 'block' | 'cache' | 'static' | 'dynamic' | 'client'
| 'defer' | 'measure' | 'ignore' | 'render' | 'upgrade' | 'disable'
export const FIX_CARD_GROUPS: Record<FixCardGroup, { label: string; color: CardColor; icon: FixCardIcon }> = {
stream: { label: 'Stream', color: 'blue', icon: 'align-left' },
block: { label: 'Block', color: 'red', icon: 'loading' },
cache: { label: 'Cache', color: 'purple', icon: 'database' },
// ... static / dynamic / client / defer / measure / ignore / render / upgrade / disable
}
export type FixCard = {
id: string // Docs anchor for this card.
title: string
group: FixCardGroup
link: string | null // Docs URL, or null for no link.
snippets: Snippet[]
copyable?: boolean // Show the Copy prompt button on this card.
}
每个错误族对应一个具名数组,例如 runtimeCards、clientHookCards、dynamicCards。以 dynamicCards(对应 uncached data 错误族)为例,它包含三张卡:wrap-in-or-move-into-suspense(group: stream)、cache-the-component-or-data(group: cache)、allow-blocking-route(group: block),每张卡的 link 都指向 nextjs.org 上对应错误页面的锚点,snippets 是卡片里展示的高亮代码行。这正是文档页中 <FixCard /> 必须逐字复制的数据源。
错误标题:工厂函数的模板字符串就是页面 title
sync-io-messages.ts 中的 createSyncIOErrorImpl(L27–L43)展示了标题的生产方式:
return new Error(
`Route "${route}": Next.js encountered the unstable value ${expression} while prerendering.\n\n` +
`This value can change between renders, so it must be either prerendered or computed later.\n\n` +
`Ways to fix this:\n` +
` - [dynamic] Render at request time by adding a dynamic data access (e.g. \`await connection()\`) before this call\n` +
` - [cache] Prerender and cache the value with \`"use cache"\`\n` +
` - [client] Render the value on the client with \`"use client"\`` + ...
)
去掉 Route "..." 前缀后的模板字符串,就是参考页 blocking-prerender-random.mdx frontmatter 中的 title: Next.js encountered the unstable value Math.random() while prerendering。同理,blocking-route-messages.ts 的 createRuntimeBodyError、createDynamicBodyError 产出的 "encountered runtime data during prerendering" / "encountered uncached data during prerendering" 等模板,分别对应 blocking-prerender-runtime、blocking-prerender-dynamic 页面的标题,且正文中列出的修复项([stream]、[cache]、[block])与框架卡片分组一一对应。
三、动手前的五步准备
- 读框架卡片数据:在 instant-guidance-data.ts 中找到匹配的
FixCard[],记下每张卡的id、title、group、link、snippets。 - 读工厂消息:找到
createSyncIOError、createSyncIOClientError、createDynamicBodyError等,标题模板(去掉Route "..."前缀)成为页面title。 - 读既有
errors/<slug>.mdx(如存在):记下每个模式、代码示例与注意事项。所有有用内容必须保留——新结构没有 1:1 位置时,迁移到 Gotchas 或 Other options。 - 读规范文档:对每个要引用的 API(
use cache、cacheLife、cacheTag、connection、Suspense、useEffect、use client、generateStaticParams等)查阅发布版文档,使用其中的精确术语。 - 应用 Vercel 技术写作风格:主动语态、sentence-case 标题、无禁用词。
四、页面结构(强制形态)
每个页面遵循完全相同的形状,不得增删或重排小节:
---
title: <literal dev-overlay headline, no period, strip Route "..." prefix>
kind: insight
---
<Instant Navigations 提示框 div —— 链接博客文章与 Ensuring instant navigations 指南的样式化框;从任意现有 insight 页面复制>
<Framing,2-3 段。第 1 段以过去时态 "During <phase>, <event>" 开头(如 "During prerendering, a Server Component called ..."),点名 API 并说明机制或后果。绝不用内联代码开头——先用一个词起句("The `params` prop ...")。后续段落承载教学,并用 "For X, see Y" 句式交叉链接兄弟页面(平行 API 族 + 客户端/服务端对应页)。>
## Ways to fix this
<FixCardGrid> 包裹每张框架卡片一个 <FixCard />,按框架顺序排列
## <Card 1 title>
Choose this fix when ...
### Patterns
### Trade-off
### Gotchas
(可选: ### Short-lived caches —— 仅用于 cache 类修复)
## <Card 2 title>
...
## <Card N title>
...
(可选: ## Other options —— 不映射到框架卡片但仍然有用的模式。示例:桥接到另一错误页的修复(客户端页面上放 "Cache the value in a Server Component" 并链接服务端页)、来自 `errors/<slug>.mdx` 上游的旧内容、彻底绕开问题的替代 API。每个 option 有自己的 `###` 标题、说明文字、代码片段、指向完整讲解页的 "Learn more" 链接)
## Verifying the fix
(规范两段 —— 见下文 "Verifying the fix" 规则)
## Don't want this validation?
(规范退出块 —— 见下文规则。
例外:同步 IO 页面用 "## Why `instant = false` doesn't clear this error" 替换,
因为退出机制无法抑制 sync-IO abort)
## Related Insights
(所有其他 insight 类错误页的完整列表,当前页省略)
参考页 blocking-prerender-random.mdx 完整展示了这一形态:开头提示框、Ways to fix this 下的 <FixCardGrid>(三张卡:Generate on every request / Cache the random value / Render on the client)、每张卡对应的修复小节(各含 Patterns/Trade-off/Gotchas,cache 卡额外含 Short-lived caches)、Verifying the fix、Why instant = false doesn't clear this error、以及按「body 错误 → metadata/viewport → 不稳定值错误(服务端后客户端)→ navigation 类」排序的 Related Insights 索引。
五、硬规则详解
5.1 Frontmatter
title= dev overlay 展示标题,无句末句号,即 overlay 显示的字符串(见 errors.tsx 中的标题字符串或 sync-io-messages.ts 等工厂),去掉Route "..."前缀,内联表达式做泛化(client-hook 的 overlay 里内联显示`useSearchParams()`,而文档标题写的是 "in a Client Component")。kind: insight必须始终存在。
5.2 ## Verifying the fix
每个页面在退出块之前都有该小节;页面不存在顶层 Good to know,页面级提示放入相关修复小节的 Gotchas。固定两段:
- 可观测检查:「After applying a fix, reload the route and confirm the page immediately paints meaningful UI, with any
<Suspense>fallbacks covering only the regions that stream in.」(navigation 类 insight 用 "navigate to the route and confirm the insight no longer appears in the dev overlay and ..." 替代 "reload the route")。随后是空壳警示句:包住整个页面主体的边界可以用一个空壳通过校验。 - 工具段落:dev overlay 指向失败组件;构建输出更简略。运行
next build --debug-prerender获取完整用户帧堆栈,next build --debug-build-paths /dashboard /settings针对特定路由迭代。措辞直接复制现有页面。
检查项要写成读者在浏览器中看到的,而不是框架产物("the static shell renders real content" 已因此被弃用——正确的修复后 fallback 是预期的,检查点在于它只覆盖流式区域)。
5.3 <FixCard> 卡片
- 所有卡片包在单个
<FixCardGrid>中(与 instant-navigation.mdx 使用同一组件)。 - 每张框架卡片一个
<FixCard />,顺序与框架FixCard[]数组一致。 title= 框架卡片标题,逐字。若作为标题读起来别扭,先改框架,绝不改文档。href=#+ 标题的自动 slug(如 "Generate on every request" →#generate-on-every-request),必须与标题自动生成的一致。group= 框架卡片分组(dynamic、cache、client、stream、defer、measure、block、render、ignore、upgrade、disable、static),与 instant-guidance-data.ts 中FIX_CARD_GROUPS的键一致。snippets= 与 instant-guidance-data.ts 中匹配卡片的snippets数组相同,逐字复制。卡片上不放描述性文字——视觉由 snippets 承担。- 自闭合标签(
<FixCard ... />),卡片无子节点。 - 不放
prompt属性。「Copy prompt」按钮在点击时由页面 URL 与卡片的title+href动态生成 prompt:Agent 收到指向规则文档并点名该修复的 prompt 后,会去读文档页(即用户当前所在的页面)获取全部约束与代码形态。这正是该技能存在的原因——文档页本身就是 prompt 的事实来源。
5.4 ## <Fix> 小节
- 标题文本 = 卡片标题,逐字,自动 slug 化为上述
href。 - 首句:「Choose this fix when
<condition>.」 ### Patterns:每个意义不同的修复形态一个####,每个含 1–2 句纯文字说明、一个简短可读的jsx filename="app/..."片段(完整、可复制、无...existing code...)、可选的Learn more:链接。### Trade-off:1 段,强制。要在本错误语境下描述权衡,而非泛泛的 API 权衡。若唯一的诚实权衡就是规范 API 行为(如「GSP 在列表变化时需要重建」),一句话带过并链到 API 参考。### Gotchas:要点列表,强制(至少 1 条)。- 可选
### Short-lived caches子节(cache 类修复记录 5 分钟阈值)。
5.5 代码片段
- 必须是合法 React。不得在 Client Component 渲染期间内联展示不稳定 API(random、time、crypto)——那会造成 hydration mismatch。应延迟到
useEffect+useState或事件处理器。 - 惰性
useState初始化器(如useState(() => someUnstableCall()))在 SSR 期间执行——必须在 Gotchas 中警告。 useRef惰性初始化模式对稳定 ID 有效(在 getter 函数中初始化,而非内联),仅适用于「计算一次并冻结」的值,不适用于「反映当前时刻」的值。- 代码块一律带
filename="app/..."。 - 当模式把渲染延迟到 hydration 之后(如
useEffect),Trade-off 必须链接 Preventing flash before hydration 指南。
5.6 交叉链接
- Framing 段:链到兄弟页面(客户端 ↔ 服务端对应页、平行 API 族)。
- Gotchas:警告 Client Component 内联渲染时链到
-client页面。 - Related Insights:所有其他 insight 类错误页的完整列表(当前页省略)。这是 Insight 族的索引而非精选短名单。顺序:body 错误 → metadata/viewport → 不稳定值错误(服务端后客户端)→ navigation 类。不要在该节加 API 参考或指南;API 与文件约定的链接应内联散布于正文。
- 跨页模式链接:当一个页面上的修复在兄弟页有深入讲解时,本页只展示最常见模式并链到兄弟页。例如服务端页的 "Render on the client" 只展示一个客户端模式并链到
-client页;客户端页的 "Other options" 桥接到服务端页的 cache 修复。不要在兄弟页间复制整个小节——每页保持精简,让兄弟页做规范参考。 - 仅第一方来源:只链
nextjs.org/docs/*、react.dev/*、developer.mozilla.org/*及其他规范第一方参考。绝不链个人博客、社区文章、会议演讲、X/Bluesky、GitHub gist 或任何第三方来源——包括作者自己的博客。若某第三方文章启发了某个模式,内化该想法并用自己的声音写出,不做引用。兄弟错误页、自家文档、一手 API 规范是唯一可接受目标。
5.7 术语(对照规范文档核验)
use cache指令(正文中不写作"use cache");Cache Components(大写);static shell(链 glossary#static-shell);cacheLife/cacheTag/revalidateTag/updateTag使用发布名;connection()来自next/server;Client Component / Server Component(大写);prerendering 首次出现必须链接 glossary。
5.8 ## Allow blocking route 规范形态
当框架卡片集包含 instant = false(group 为 block,如各 *Cards 数组中的 allow-blocking-route 卡)时,使用规范的小节形态。所有含此修复的页面必须一致——偏离形态会让页面读起来像离群值。
引言:1 段说明 instant = false 做什么及权衡。可选第 2 段说明何时它「很少」是正确答案(例如客户端 hook 或 cache 修复中 Suspense 边界几乎总是可行的场景),并直接表述其罕见性。
模式:页面 body 类错误(runtime data、uncached data、client hooks)同时用 #### Opt the page out 与 #### Opt the layout out;viewport 类错误只用 #### Opt the layout out(viewport 永远在 layout 上)。每个模式含 1–2 句适用时机说明、展示导出语句的 jsx filename="app/..." 片段、Learn more: 链接。模式片段之后放 "Use either pattern when:" 要点列表(2 条:layout shell 无意义 + 增量迁移;viewport 页只有 1 个模式时用单数表述),以及一句收尾:"Don't use this to dismiss the error. Choose Sibling fix A or Sibling fix B when either is feasible."
Trade-off:1 段,「Navigations to this route are not instant. The user waits for the full server render before any HTML arrives. Use this only when that latency is the deliberate cost of the route's purpose.」
Gotchas(强制,按此顺序):
instant = false只对导出它的 segment 生效;后代 segment 仍按自身配置或全局默认值校验。- 该导出不禁用预渲染。路由仍会尽可能预渲染,只是退出即时导航校验。
- 页面特定 gotcha(如 viewport 页追加框架合成路由的 gotcha)放在两条规范要点之后。
绝不在面向用户的正文中写「Confirm with the user that ...」类要点——页面是给用户读的,写给他们,而不是给 Agent。Agent 应应用的护栏属于 ### Patterns 下的代码形态指导(Agent 通过复制的 prompt 中的文档链接读到同一页面)。
5.9 ## Don't want this validation? 规范退出块
每个 insight 页面在 ## Related Insights 之前以规范退出块收尾——六个同步 IO 页面(random/current-time/crypto 及其 -client 变体)除外,它们用 ## Why instant = false doesn't clear this error 替换,因为 sync-IO abort 发生在预渲染路径上,退出机制无法抑制它(参见 blocking-prerender-random.mdx 末尾的同名小节)。退出块说明即时导航校验在 Cache Components 应用中默认运行,并给出按 segment、按子树、按应用三种退出方式。逐字复制:
## Don't want this validation?
Instant-navigation validation runs by default in Cache Components apps and is what surfaces this error.
- **One segment**: add `export const instant = false` to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
- **Entire app**: set `experimental.instantInsights.validationLevel` to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `instant`.
See Ensuring instant navigations for the full model.
5.10 写作风格
- 每节以答案开头:「Choose this fix when ...」。
- Sentence-case 标题,无句末句号;不用破折号表强调。
- 禁用词:
easy、quick、simple、just、very、basically、obviously、utilize、facilitate、leverage、robust、seamless、cutting-edge、innovative。 - 无填充语:
In this guide ...、As mentioned above ...、Let's take a look at ...、It's worth noting ...。 - 主动语态 + 直接称呼读者:「You wrap the component」而非「the component is wrapped」。
- 模式上不加 "Default." 标签(评审阶段已移除——模式没有默认)。
- 散文不用分号——拆成两句,或用 ", and" 做省略式对比。
- 绝不用内联代码开头——先用一个词起句("The
paramsprop ...")。 - 标题中的代码仅在指代真实 API 且大小写精确时允许(
await connection()、cacheLife);概念保持散文("Opt the page out")。 Learn more:链接文本:指南用目标页精确标题("Streaming"、"Ensuring instant navigations");API 参考用与文档标题一致的不带括号的裸代码名([connection]、[io]、[searchParams]);第三方 API 保留规范拼写([performance.now()])。
六、审计清单
审计现有页面时逐项核对:
- [ ]
title= overlay 展示标题,无句号,内联表达式已泛化 - [ ] frontmatter 含
kind: insight - [ ] 无顶层 Good to know;存在
## Verifying the fix且含两个规范段落(可观测检查 +--debug-prerender工具) - [ ] 所有卡片包在单个
<FixCardGrid>中 - [ ] 每张框架卡片一个
<FixCard />,且按框架顺序 - [ ] 每个
<FixCard />的title与卡片标题逐字一致 - [ ] 每个
<FixCard />的href=#+ 标题自动 slug - [ ] 每个
<FixCard />的group与框架卡片分组一致 - [ ] 每个
<FixCard />的snippets与框架卡片snippets数组逐字一致 - [ ] 任何
<FixCard />上无prompt属性——复制按钮从title+href+ 页面 URL 生成 prompt - [ ]
<FixCard />自闭合(无子节点、无描述文字) - [ ] 每个
## <Fix>标题与卡片标题逐字一致 - [ ] 每个修复小节含
### Patterns、### Trade-off、### Gotchas - [ ] 模式上无 "Default." 标签
- [ ] 页面任何位置无
Confirm with the user ...表述 - [ ] 含
## Allow blocking route的页面符合规范形态(body 类错误用双模式;viewport 仅 Opt the layout out;"Use either pattern when" 列表;"Don't use this to dismiss the error" 收尾;规范 2 条 Gotchas) - [ ] 代码片段是合法 React(Client Component 渲染期间无内联
Math.random()) - [ ] Gotchas 中对
useState(() => Math.random())有警告 - [ ] 所有 API 参考在正文中内联链接
- [ ] Framing 段交叉链接兄弟页面(正文内联链接承载大部分 API 引用)
- [ ] 存在
## Don't want this validation?,逐字符合规范块(同步 IO 页为## Whyinstant = falsedoesn't clear this error) - [ ] 存在
## Related Insights,列出所有其他 insight 类错误页(当前页省略) - [ ] 上游
errors/<slug>.mdx内容已保留(必要时迁移至 Gotchas 或 Other options) - [ ] 术语与规范文档一致(已核验,非假设)
- [ ] 已应用 Vercel 技术写作风格(无禁用词、主动语态、sentence-case 标题)
- [ ] 框架卡片
linkURL 指向正确的标题自动 slug(若不符,标记为框架侧待办) - [ ] cache 类修复下存在 Short-lived caches 子节(如适用)
- [ ] 散文无分号;无段落以代码开头
- [ ]
Learn more:文本符合标题/裸 API 约定,且每个目标都是该模式的最佳页面
七、文件位置与参考页
- 新页面:
errors/<slug>.mdx(本仓库),发布后 URL 形如nextjs.org/docs/messages/<slug> - nextjs.org 每次部署从 canary 克隆
errors/(同步管道位于vercel/front,不在本仓库) - 框架卡片:packages/next/src/next-devtools/dev-overlay/components/instant/instant-guidance-data.ts
- 工厂消息:packages/next/src/server/app-render/sync-io-messages.ts、packages/next/src/server/app-render/blocking-route-messages.ts
规范参考页是 errors/blocking-prerender-random.mdx。编写新页面时先读它,以匹配其精确结构、语气与细节深度:从 Math.random() 这个具体 API 出发,三个修复小节(每请求生成 / 缓存随机值 / 客户端渲染)各配完整可运行的 app/... 代码片段,cache 小节附 Short-lived caches 子节解释 5 分钟阈值,末尾以「Why instant = false doesn't clear this error` 说明同步 IO 错误的特殊性,Related Insights 按族排序索引全部 15 个兄弟页。这一页面是整套技能规则的活体样板。
八、小结:文档页是 overlay 与 Agent 之间的单一事实来源
这套技能的设计闭环在于:dev overlay 的 FixCard 数据(instant-guidance-data.ts)与错误标题(sync-io-messages.ts 等工厂)是「机器侧」事实;errors/<slug>.mdx insight 页面是「人读 + Agent 读」的文档侧事实。卡片上不带 prompt 属性而靠页面 URL + 卡片标题动态生成 prompt,意味着文档页本身成为 Agent 修复建议的唯一依据——这也解释了为何结构、术语、交叉链接、代码片段合法性都要作为硬规则约束。对贡献者而言,掌握「来源链 → 五步准备 → 强制页面结构 → 硬规则 → 审计清单」这条流水线,即可独立编写或审计 Next.js 仓库中任意一个 insight 类错误页面。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00