- 发布日期
第 40 讲:给 Next.js 主仓库提 PR:从克隆到合并
从 Fork 到合并:给 Next.js 主仓库贡献 PR 的完整流程与注意事项
整门课的最后一讲。前 39 讲让你"读懂"和"用好"Next.js,这一讲带你正式参与主仓库:fork → 找 issue → 改代码 → 加测试 → 跑 CI → review → merge。并附一份"如何长期跟踪 Next.js 演进"的清单,让你毕业后也能持续深入。
学习目标
- 知道 Next.js 仓库的协作流程、PR 模板、CI 规则。
- 学会用
pnpm new-test/pnpm test-dev-turbo/pr-status.js等内部工具。 - 完整走一遍 "找 issue → 复现 → 修复 → 测试 → PR → review → merge"。
- 知道维护者关注什么、PR 容易被拒的常见原因。
- 拥有一份长期成长路线:从 good-first-issue 到能 review 别人的 PR。
一、准备工作
1.1 仓库结构(对应第 2 讲,再回顾一次)
next.js/
├── packages/ ← published npm packages
│ ├── next/ ← 主框架
│ ├── next-codemod/ ← 升级 codemod
│ ├── next-swc/ ← Rust 绑定
│ ├── eslint-plugin-next/
│ └── ...
├── turbopack/ ← Turbopack(Rust)git subtree
├── crates/ ← SWC custom transform Rust
├── test/ ← 全部测试
│ ├── e2e/
│ ├── development/
│ ├── production/
│ └── unit/
├── examples/ ← 示例项目
├── docs/ ← 文档
└── scripts/ ← 维护脚本
1.2 fork & clone
# fork on github first
git clone git@github.com:<your-handle>/next.js.git
cd next.js
git remote add upstream git@github.com:vercel/next.js.git
git fetch upstream
git checkout -b my-fix upstream/canary
canary 是开发主干,所有 PR 都对 canary。
1.3 安装依赖
corepack enable
pnpm install
packages/next-swc/native/ 会自动下载预构建的 .node 二进制(你无需装 Rust 工具链——除非你改 Rust 代码)。
1.4 构建
# 改了 packages/next/** 后
pnpm --filter=next build
# 改了 Rust(turbopack / next-swc)
pnpm build-all
第一次完整 build 大约 1-2 分钟(视机器);后续增量 build 几秒。
watch 模式:
pnpm --filter=next dev
# 监听 src 改动自动重编译
二、找 issue
2.1 good-first-issue / help-wanted
打开 https://github.com/vercel/next.js/labels/good%20first%20issue 看 open 列表。
筛选原则:
- 描述清晰,有复现
- 不涉及 Vercel 内部基础设施
- 不依赖一堆背景知识("先理解 RSC 整个架构"那种先放放)
- 维护者已确认是 bug(不是讨论中的 feature request)
2.2 自己用过程中发现的 bug
最好的来源。你正在用 Next.js 做项目,遇到一个具体异常 → 写最小复现 → 搜 issue → 没有就开新 issue → 自告奋勇修。
2.3 复现验证
任何 PR 第一步都要能在 main 复现 bug:
# 用 test 复现
pnpm new-test -- --args true my-bug-repro e2e
# 这会在 test/e2e/my-bug-repro/ 生成模板
# 编辑 fixture 和测试用例
# 跑测试
NEXT_SKIP_ISOLATE=1 pnpm test-dev-turbo test/e2e/my-bug-repro/
如果用现成 issue,可以直接拷贝 issue 里附的 repro 进 test/e2e/issues/{issue-number}/。
三、写修复
3.1 定位代码
整门课讲了几十个源码入口,回顾一下定位思路:
- bug 在 dev mode?→
src/cli/next-dev.ts+src/server/dev/ - bug 在 prod mode?→
src/cli/next-start.ts+src/server/next-server.ts - bug 在 build?→
src/build/index.ts - bug 在 RSC render?→
src/server/app-render/ - bug 在 client navigation?→
src/client/components/ - bug 在 manifest 生成?→
src/build/webpack/plugins/
不知道从哪入手时:
# 搜错误消息字符串
rg "exact error message" packages/next/src
# 搜 spans 名找入口
rg "BaseServer\.\w+" packages/next/src
3.2 边写边测
watch 模式 + NEXT_SKIP_ISOLATE=1 是最快循环:
# 终端 A
pnpm --filter=next dev
# 终端 B
NEXT_SKIP_ISOLATE=1 NEXT_TEST_MODE=dev pnpm testheadless test/e2e/my-bug-repro/foo.test.ts
改一次 src/ → watch 自动重编 → 跑测试 → 1-5 秒反馈。
⚠️ 模块解析改动不能用 NEXT_SKIP_ISOLATE=1(第 0 讲 / CLAUDE.md 强调过)。要验证 publish 后能否解析时,去掉这个 env。
3.3 写新测试
测试必须覆盖你修的 bug。模板:
// test/e2e/issues/12345/issue-12345.test.ts
import { nextTestSetup } from "e2e-utils";
import { retry } from "next-test-utils";
describe("issue #12345", () => {
const { next } = nextTestSetup({
files: __dirname, // 用同目录下的 fixture
});
it("should render correctly when X", async () => {
const browser = await next.browser("/");
await retry(async () => {
const text = await browser.elementByCss("h1").text();
expect(text).toBe("expected");
});
});
});
注意(来自 CLAUDE.md):
- 用
retry()而非setTimeout - 用
nextTestSetup({ files: __dirname })而非 inline files - 不要用 deprecated
check()
3.4 写好 commit message
Next.js 不强求 conventional commits,但简明扼要的 imperative 风格是惯例:
Fix incorrect cache key for static params in App Router
When generateStaticParams returns nested objects, the cache key was
constructed without normalization. Add a stable JSON serialization
to ensure consistent keys across builds.
Fixes #12345
不超过 50 字符的 subject,blank line,详细 body,可选 Fixes # 链接 issue。
四、PR 提交流程
4.1 推 fork
git push -u origin my-fix
4.2 打开 PR
GitHub 会弹"open pull request"。注意:
- target branch 选
canary - 标题简明:
fix: correct cache key for nested static params - 描述区会自动套用模板
.github/pull_request_template.md
模板里要填:
- What? 简述变更
- Why? 链接 issue 或解释动机
- How? 关键设计/取舍
- 复选框:documentation updates / tests / breaking change
- 末尾保留隐藏注释(团队 LLM 标记):
<!-- NEXT_JS_LLM_PR -->
⚠️ PR 默认 draft。维护者建议留 draft,准备好后再 ready for review。你不要自己点 "ready for review",让维护者决定(CLAUDE.md 明确说)。
4.3 等 CI
PR 提交后跑大量 CI job:
build / linttypesprettiereslint- 各种 mode 的 test(dev / prod × turbo / webpack)
rust check / build如果你动了 Rust
CI 一般 15-30 分钟跑完。失败时:
# 在本地复现 CI 失败
node scripts/pr-status.js # 自动识别当前分支对应的 PR
# 生成 scripts/pr-status/ 下的分析文件
按 CLAUDE.md 里 $pr-status-triage skill 的优先级:build > lint > types > tests。先修阻塞的。
4.4 review 流程
维护者(如 @timneutkens / @ijjk / @feedthejim / ...)会 review。常见反馈:
- "Please add a test case for X"
- "This logic should live in Y file instead"
- "Performance: avoid allocating in hot loop"
- "Consider edge runtime"
- "Breaking change—needs codemod"
针对反馈:
- 在分支上加 commit(不要 squash 后强推)
- 留言回复每一条
- 改完 ping reviewer
4.5 合并
通过 review + CI 全绿 + 维护者批准 → 维护者 squash & merge 进 canary。
下个 canary 发版(几乎每天)你的修复就 ship 了。约 2-4 周后随主版本进 stable。
五、维护者关注什么(写 PR 前对照)
按从大到小:
| 关注点 | 解释 |
|---|---|
| 正确性 | 没引入新 bug;现有测试不挂;新增测试覆盖 |
| 性能 | hot path 无 deopt(第 36 讲);bundle size 不上涨 |
| DX | 错误信息友好;dev overlay 体验好 |
| Edge 兼容 | 改动是否在 edge runtime 跑得动;是否过早 import node:* |
| Stable API | 不破坏现有 public API;experimental flag 标好 |
| 代码风格 | 跟周围代码一致;不引入新依赖 |
| 测试质量 | 测试稳定不 flaky |
六、PR 容易被拒的常见原因
| 原因 | 改进 |
|---|---|
| 没有测试覆盖 | 必须加 e2e 或 unit 测试 |
测试不稳定 / 用 setTimeout | 改用 retry() |
| 引入新依赖 | 通常 reject;除非必要并讨论过 |
| 大改架构未先讨论 | 先开 RFC 或 issue 讨论 |
| 改 internal API 但没改对应 callsite | 找全所有调用方 |
| breaking change 没提供 codemod | 大版本里通常要求 codemod |
| 只改了 webpack 没改 turbopack(或反之) | 两条线都要顾 |
| commit 历史混乱(强推、合并提交) | 保持 clean linear history |
七、内部工具速查
| 命令 | 用途 |
|---|---|
pnpm new-test -- --args true my-feature e2e | 生成新测试模板 |
pnpm test-dev-turbo <path> | dev + turbopack 跑测试 |
pnpm test-start-webpack <path> | prod + webpack 跑测试 |
pnpm testheadless <path> | 不重 build,直接跑测试 |
pnpm --filter=next types | 类型检查(10s 比 build 60s 快) |
pnpm lint / pnpm lint-fix | lint |
pnpm prettier-fix | format |
node scripts/pr-status.js | 拉取本分支 PR 的 CI 状态 |
cargo fmt | Rust 格式化 |
八、长期成长路线
毕业后想持续深入:
8.1 follow 通道
- GitHub Watch vercel/next.js → 选 "Custom" → Releases + Discussions
- 订阅 https://nextjs.org/blog
- Twitter: @leeerob @timneutkens @feedthejim @sebmarkbage(React core)
- 每周看
git log --oneline upstream/canary了解动态
8.2 阅读 RFC / Discussions
vercel/next.js/discussions 是 RFC 区。重大架构变化(如 PPR、cache components)都先在这里讨论。早期介入有助于:
- 知道下一个 major 会有什么
- 提建议影响设计
- 早晚体验,先你团队一步用上
8.3 升 contributor → reviewer
- 持续提合理 PR,建立信誉
- review 别人的 PR(任何人都能 review,不需要权限)
- 在 discussion 里答疑
- 半年-1 年后可能拿到 triager / collaborator 权限
8.4 持续阅读源码的节奏建议
每次新版发布:
- 看 release notes,挑 2-3 个让你好奇的功能
- 找对应 PR,读 diff
- 把 diff 涉及的目录读一遍
- 写一份笔记
3-5 个版本后你会对 Next.js 内部建立起非常完整的 mental model。
九、配套 fixture
fixtures/lecture-40/ 是一份 "PR 模板演练":
MOCK_ISSUE.md:假 issue 描述MOCK_REPRO/:最小复现 Next.js fixtureMOCK_PATCH.diff:示意性补丁MOCK_TEST.test.ts:测试模板
不能真的对接 GitHub workflow(需要 vercel/next.js 权限),但作为"流程演练"足够。
启动:
cd learning/nextjs-40-lectures/fixtures/lecture-40
# 阅读所有 MD 文件,按 README 步骤模拟
十、本讲与全课总结
整门 40 讲走完,你应该具备:
| 能力 | 达成 |
|---|---|
| 对 Next.js 整体架构有完整 mental model | ✅(讲 1-4 / 15-22) |
| 阅读 Next.js 任意源码模块 | ✅(贯穿全部) |
| 排查生产环境常见问题 | ✅(讲 33-38) |
| 优化 Next.js 应用性能 | ✅(讲 35-36) |
| 给 Next.js 写扩展(adapter / cache handler / codemod) | ✅(讲 39) |
| 给 Next.js 主仓库提 PR | ✅(本讲) |
整门课讲的范围已经覆盖了 Next.js core team 公开的 99% 知识点。剩下 1% 在两个地方:
- Vercel 内部基础设施(serverless runtime、edge 路由、CDN)—— 不公开
- 正在开发中的新功能(cache components、新 DX、新 RSC API)—— 跟 RFC
恭喜走完 40 讲!现在你不仅是"会用 Next.js 的人",是"懂 Next.js 内部、能扩展 Next.js、能给 Next.js 贡献"的人。
长期建议
整门课的核心思想:抽象只有读了实现才能真懂。Next.js 是个完美样本——它把现代前端最复杂的几件事(RSC、Streaming、Build pipeline、Edge runtime、Caching)都做成生产级代码。任何对你不清楚的地方,去读源码,永远比读文档讲得更准确。
希望未来你不仅自己用得溜,也能成为团队里讲清楚 Next.js 的人,乃至给社区贡献价值。
— 第 40 讲完 —