Next.js 16 会往你项目里写一个文件,专门写给 AI 看

next dev 检测到 AI 编程工具在运行时,会自动生成并维护 AGENTS.md,指向随包安装的 4.1MB 官方文档。这是框架对抗模型训练数据滞后的一次正面尝试。

2026-09-20

用 create-next-app@16 初始化项目,你会在根目录看到一个陌生的文件:AGENTS.md 。打开只有七行:

<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all
differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/`
before writing any code. Heed deprecation notices.

<!-- END:nextjs-agent-rules -->

翻译过来:这不是你认识的那个 Next.js,写代码前先去读 node_modules 里的文档。

这句话不是写给你的,是写给 AI 的。

它是怎么出现的

把它删掉,跑一次 pnpm dev ,它会回来。

源码在 node_modules/next/dist/server/lib/generate-agent-files.js ,触发点在 start-server.js :

if (isDev) {
  // Gated on `agentRules` in next.config (default true).
  if (initResult.agentRules !== false) {
    const result = await ensureAgentRulesForDev(dir)
    // ...
  }
}

只有 next dev 会写文件,next build 和 next start 不碰。开关是 next.config.ts 顶层的 agentRules ,默认开启(判断写的是 !== false ,不配置即生效)。

再往里一层,是两道闸门:

async function ensureAgentRulesForDev(dir) {
  if (await getAgentName() === null) return null
  if (hasCurrentAgentRules(dir)) return null
  return writeAgentFiles(dir)
}

第一道:判断是不是 AI 在跑。 实现在 @vercel/detect-agent ,纯粹是读环境变量,按顺序短路返回:

环境变量判定
CURSOR_TRACE_IDcursor
CLAUDECODE / CLAUDE_CODEclaude
CODEX_SANDBOX / CODEX_THREAD_IDcodex
GEMINI_CLIgemini
COPILOT_MODELgithub-copilot
AI_AGENT通用逃生口,值即 agent 名

你自己开终端跑 pnpm dev ,这些变量全是空的,什么都不会发生。只有 AI 工具在跑,它才动手。

第二道:判断块是不是过期了。 比对方式是整块逐字符相等,不是「有没有这个标记」。所以 Next.js 一升级、文案一改,旧块立刻失配,进入更新流程。

写进哪个文件,有一套优先级

if (agentsMdExists && (agentsMdHostsBlock || !claudeMdHostsBlock)) {
  return { agentsMd: upsertFile(agentsMdPath, block), claudeMd: 'skipped' }
}
if (claudeMdExists) {
  return { agentsMd: 'skipped', claudeMd: upsertFile(claudeMdPath, block) }
}
// Neither file exists — scaffold both, matching create-next-app.
fs.writeFileSync(agentsMdPath, block + '\n', 'utf-8')
fs.writeFileSync(claudeMdPath, CLAUDE_MD_CONTENT, 'utf-8')   // 内容只有一行:@AGENTS.md
现状结果
有 AGENTS.md ,块在它里面更新 AGENTS.md
有 AGENTS.md ,块在 CLAUDE.md 里尊重现状,更新 CLAUDE.md
只有 CLAUDE.md写进 CLAUDE.md
两个都没有建 AGENTS.md 放块,再建一个只有 @AGENTS.md 一行的 CLAUDE.md

最后一支有个坑:那个 CLAUDE.md 是 writeFileSync 全量覆盖。如果你自己写了一份 CLAUDE.md 项目规范,又恰好没有 AGENTS.md ,跑一次 next dev ,你的规范会被覆盖成一行 @AGENTS.md 。

我初始化项目时为了保住自己的 CLAUDE.md ,先把它挪走再跑脚手架,事后再覆盖回来 —— 如果当时连 AGENTS.md 一起弄丢,就正好撞上这一支。结论:这两个文件任何时候都别同时缺席。

更新块的方式很克制

const startIdx = existing.indexOf(AGENT_RULES_START_MARKER)
const endIdx = existing.indexOf(AGENT_RULES_END_MARKER)
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
  return existing.slice(0, startIdx) + normalizedBlock
       + existing.slice(endIdx + AGENT_RULES_END_MARKER.length)
}

切片替换,只动两个 marker 之间。你在块的上下写自己的内容是安全的。另外:

  • 内容完全相同就返回 'unchanged' 且不写盘,不会平白刷新 mtime 触发 dev server 的文件监听
  • 先检测文件本身的换行风格(有 \r\n 就按 CRLF),块跟随文件,避免 Windows 上反复重写
  • 有个 while(true) 循环清理旧版本 codemod 留下的 marker,防止升级后新旧两块并存

真正的货在 node_modules/next/dist/docs/

AGENTS.md 只是个路标,它指向的才是重点:随 Next.js 一起安装的 456 个官方文档 md 文件,4.1 MB,与安装版本严格对应。

官方愿意让每次安装多付 4MB,动机很直接:模型的训练数据会过期,node_modules 不会。

Next.js 16 相对 14/15 的破坏性变更密度太高了。我按这个约定查了一个问题 —— PPR 在 16 里是什么状态 —— 结果和网上大多数文章都不一样:

grep -rli "partial prerender" node_modules/next/dist/docs/

cacheComponents.md 里写得很清楚:

Additionally, cacheComponents implements Partial Prerendering (PPR) as the default behavior in the App Router. This means the experimental.ppr configuration flag and the experimental_ppr route segment configuration are no longer necessary and have been removed.

也就是说:PPR 本身不再是实验特性,但它被并进了 Cache Components;cacheComponents: true 仍需手动开启,不是默认值;不开则沿用旧缓存模型。

如果我凭记忆写「PPR 还是 experimental,要配 experimental.ppr」,或者反过来说「16 里 PPR 默认开了」——两种说法都错,而且都是很容易脱口而出的错。

值得注意的一个副作用

同一个 agent 检测结果还会进遥测。node_modules/next/dist/telemetry/anonymous-meta.js :

{
  // ...
  isCI: isCI,
  nextVersion: '16.3.5',
  agentName: await getAgentName(),
}

Vercel 在统计「有多少 Next.js 项目是 AI 写的」。介意的话 next telemetry disable 关的是这个,和 agentRules 是两回事 —— 后者管写文件,前者管上报。

我的看法

这个设计解决的是一个真问题:框架迭代速度已经超过了模型训练数据的更新速度。 与其指望模型记住每个版本,不如把版本正确的事实源放在它一定会读到的地方。

有意思的是实现方式的克制 —— 有环境检测、有幂等、有 marker 隔离、有换行符处理、有开关,但整个文件不到 200 行。比起在文档站上呼吁「请 AI 先读文档」,往 node_modules 里塞 4MB 然后在项目根放个路标,务实得多。