首页
/ Next.js 仓库贡献指南:本地开发、构建、测试与发布的全流程实践

Next.js 仓库贡献指南:本地开发、构建、测试与发布的全流程实践

2026-09-06 12:00:23作者:裘旻烁

本文以 Next.js 官方仓库的贡献指南 contributing.md 为主体,系统梳理向 Next.js 提交代码的完整流程:从选取合适的 issue、配置签名提交,到克隆仓库、pnpm dev 本地开发、pnpm build 构建、pnpm test-* 运行测试,再到 pack-next 在真实应用中验证改动、补充错误链接与 Lint 规范,以及 stable / canary 两条发布通道的工作方式。读完后,你可以独立完成一个 Next.js PR 从环境搭建到提交合并前自查的全过程。

一、贡献的入口与原则

contributing.md 开篇给出两条基本原则:

  • 提交 PR 前,先检索已有的 PR 和 issue,确认没有已打开或已关闭的相关条目;
  • 从 issue 追踪器入手,good first issue(适合新手)和 Documentation(文档)标签是很好的切入点,但任何打开状态的 issue 都可以参与。

指南明确:处理 issue 无需事先获得许可;但对新特性(feature),必须先在 Discussions 的 ideas 类别中发起提案并被接受后才能动手实现。

1.1 为什么新特性要先走讨论

贡献新特性 一文档解释了讨论机制的目标:

  1. 验证需求有效性:社区可以投票,高票需求更可能被考虑;
  2. 理解后续影响:任何加入 Next.js 的特性都会长期存在并且必须被维护,新特性要覆盖多种用例、考虑对生态的影响;
  3. 理解现状的历史原因:某个特性缺失、或现有实现是某种形态,往往有历史理由。Next.js 对不破坏已有特性(不 breaking)有强策略,新特性必须以支持渐进式采用的方式引入。

Next.js 团队自身也使用 RFC(Request For Comment)流程,可以作为高质量特性提案的参考范例。

1.2 琐碎改动(Trivial changes)会被拒绝

每一个 PR 都需要维护者审查。指南直言不讳地指出:审查者的注意力是该项目最稀缺的资源,因此像拼写修正、纯格式化、代码风格调整这类琐碎 PR 很可能被直接关闭。相对地,docs/ 目录下的文档类小改动更受欢迎,可以优先从文档贡献入手。

1.3 提交必须签名(Signed commits)

该仓库在受保护分支上要求经过 GitHub 验证的提交签名。贡献前需要:

  • 配置 Git,使用 GitHub 已验证的 GPG、SSH 或 S/MIME 密钥对提交签名;
  • 未签名的提交会被仓库规则拒绝,必须重签后才能合并;
  • 若 PR 中混入未签名提交,需要重新签名并 force-push 分支,并确认密钥已添加到 GitHub 账户、提交显示为 Verified
  • 特别注意:commit message 里的 Signed-off-by不能替代上述签名要求。

二、本地开发环境搭建

开发流程细节见 developing.md。几个关键前提(以仓库实际配置为准):

  • 开发分支是 canary所有 PR 都应面向 canary 分支提交canary 分支的变更会定期发布到 npm 的 @canary 标签;
  • Node.js 版本由 .node-version 固定为 v20,建议使用 fnm 或 nvm 按该文件选择版本,与 CI 保持一致;
  • 包管理器由 package.jsonpackageManager 字段声明为 pnpm@10.33.0。pnpm 默认遵循该字段,可保证本地与 CI 行为一致。

2.1 JavaScript 依赖

corepack enable pnpm
# 或
npm install -g pnpm@latest

可选安装 fnm/nvm(对齐 CI 的 Node 版本)以及 GitHub CLI(后续 gh pr create 会自动 fork 并创建远程分支)。

2.2 Rust 依赖(仅当要改 Rust 代码时)

# Linux:安装 C 编译器
sudo apt install build-essential

# macOS:安装 Xcode 命令行工具
xcode-select --install

通过 rustup 安装 Rust 与 Cargo。如果只改 JS/TS,可以跳过这一步。

2.3 克隆、开发、提交的标准工作流

# 1. blobless clone 加速克隆(https 或 ssh 方式均可,加 --single-branch)
git clone https://github.com/vercel/next.js.git --filter=blob:none --single-branch

# 2. 基于默认分支 canary 创建开发分支
git switch --create MY_BRANCH_NAME

# 3. 安装依赖
pnpm install

# 4. 用 Turborepo 启动开发并监听 JS 代码变更(也可改用 next build 按需构建)
pnpm dev

# 5. 改了 Rust(如 Turbopack)时重建 napi 绑定
pnpm swc-build-native            # 调试构建
pnpm swc-build-native --release   # release 模式(基准测试用)
pnpm build-all                    # 同时构建 JS 与 Rust 变更

# 6. 新终端中编译类型声明文件(类型过期后需重复执行)
pnpm types

# 7. 提交
git add .
git commit -m "DESCRIBE_YOUR_CHANGES_HERE"

# 8. 创建 PR
gh pr create

从源码脚本看,这些命令背后的实现分别是:swc-build-native 对应 build-native 脚本(经由 tsx 执行),sweep 对应 sweep 脚本pack-next / unpack-next / patch-next 分别对应 pack-next.tsunpack-next.tspatch-next.ts

2.4 两个容易踩坑的细节

NODE_ENV__NEXT_DEV_SERVER 的区别next devnext build --debug-prerender 产出的 bundle 都是 NODE_ENV=development。源码中判断"开发 bundle 中保留、生产 bundle 中剔除"的代码应使用 process.env.NODE_ENV !== 'production'(构建期检查);而"只在 dev server 运行时执行、在 next build --debug-prerendernext start 时不执行"的代码应使用 process.env.__NEXT_DEV_SERVER

清理磁盘:Rust 构建产物增长很快,可用 pnpm sweep 清理旧产物,同时会清理 pnpm store、cargo 等缓存并执行 git gc

三、构建体系:pnpm build 到底做了什么

构建文档见 building.md。一条 pnpm build 会构建 Next.js 本体、全部类型定义和各个包。从 package.json 的脚本定义看,它实际执行 turbo run build --remote-cache-timeout 60 --summarize true,借助 Turborepo 并行调度。构建过程由三个主要任务组成:

  1. 用 SWC 编译 TypeScript 源码:默认安装并使用最新 canary 的 next-swc 二进制来编译 TS 源码,产物落在 packages/next/dist/...,包含编译后的 JS 文件和 source map;
  2. 用 Webpack 打包:基于上一步的编译产物进行打包,配置位于 next-runtime.webpack-config.js
  3. 生成类型定义:使用 TypeScript 的 tsc 编译器,可单独执行 pnpm types。实际使用 tsconfig.build.json,它继承基础 tsconfig.json 但排除了测试文件等不需要的类型定义。

指南还提示:Next.js 使用 taskr 来并行化构建任务,任务定义位于 taskfile.js,每个任务名对应其中要执行的函数名,例如 taskr release 会执行 release() 函数。

3.1 处理 Turbopack / WASM / 其他 Rust 代码

  • 正在开发 Rust 代码、或想测试尚未发布为 canary 的最新 Rust 代码时,安装 Rust 后运行 pnpm swc-build-native
  • 本地测试 wasm 构建:安装 wasm-pack 后,运行 pnpm --filter=@next/swc build-wasm --target <wasm_target> 构建,再用 node ./scripts/setup-wasm.mjs 拷贝进 node_modules,最后用 NODE_OPTIONS='--no-addons' 启动 next 强制走 wasm 二进制;
  • 需要清理项目时使用 pnpm clean(对应 lerna clean + 各包 clean 任务 + 删除 dist)。

四、测试体系:隔离、分层与调试

测试文档见 testing.md。运行测试前必须先构建项目pnpm build)。官方推荐使用"目录模式"指定测试范围,例如:

# 生产模式(next build + next start)跑 app-dir/app 测试套件
pnpm test-start test/e2e/app-dir/app/

# 开发模式(next dev)
pnpm test-dev test/e2e/app-dir/app/

# 调试单个测试时换成 testonly-start,可见浏览器窗口(test-start 是 headless 后台运行)
pnpm testonly-start test/e2e/app-dir/app/

package.json 脚本定义可印证各命令的差异:test-start / test-dev / test-dev-turbo 都经由 run-jest.sh 启动,只是 --mode(start/dev)与 --bundler(webpack/turbo)参数不同;testonly-* 系列不带 --headless,因此能弹出浏览器窗口方便调试。

4.1 e2e 测试的隔离机制

test/e2etest/productiontest/development 下的测试与仓库完全隔离运行:运行时会在系统临时目录(如 /tmp)中创建一个本地版本的 Next.js,并链接到一个隔离的应用副本;随后在随机端口启动服务器执行测试,结束后销毁服务器并删除临时目录。这套逻辑由 nextTestSetup 自动处理。

4.2 测试类型与编写规范

  • pnpm new-test 从对应测试类型模板创建新测试(turbo gen test);
  • 测试分层:e2e(跑 next devnext start 及 Vercel 部署)、development(只跑 next dev)、production(只跑 next start)、integration(历史位置,尽量不再新增,因为不隔离)、unit(极快、无需浏览器或 next 进程、只测某个具体工具函数);
  • e2e / development / production 测试都应使用 nextTestSetup,它会创建隔离的 Next.js 安装,防止测试意外依赖 monorepo 内部状态;
  • 新测试套件一律用 TypeScript(.ts,unit 测试可用 .tsx);
  • 最佳实践:条件断言可能耗时等待时,用浏览器 waitForElementnext-test-utilscheck 工具等待;应用修复时,先确认"没有该修复时测试会失败",以确保回归捕获能力;
  • 若已有与待测功能紧密相关的测试套件(如 hash 导航应并入现有导航测试),把新检查加进既有套件而不是新开。

4.3 调试用的环境变量

变量 作用
NEXT_TEST_SKIP_CLEANUP=1 保留测试临时目录,可进入其中运行 pnpm debug 调试完整搭好的测试项目
NEXT_SKIP_ISOLATE=1 跳过隔离安装,直接在 Next.js 仓库内运行(部分测试不兼容,但可加速本地调试)
NEXT_TEST_MODE 手动指定 e2e 测试模式(不使用 test-dev / test-start 时)
NEXT_TEST_DEPLOY_URL 配合 pnpm test-deploy 跳过 Vercel 部署步骤,直接对已有部署 URL 跑断言
NEXT_TEST_PREFER_OFFLINE=1 测试安装依赖时给包管理器加 --prefer-offline,适合弱网环境

其他调试手段:

  • CI 失败时会尝试捕获 Playwright trace,下载解压后可用 pnpm playwright show-trace ./path/to/trace 查看;
  • 给 next 挂 Chrome 调试器:在 nextTestSetup 中传 startArgs: ['--inspect']
  • 调试测试进程本身:给运行 jest 的 node 进程加 --inspect,例如 IS_TURBOPACK_TEST=1 TURBOPACK_DEV=1 NEXT_TEST_MODE=dev node --inspect node_modules/jest/bin/jest.js ...
  • 测试性能剖析:加 NEXT_TEST_TRACE=1 启用 profiling。

4.4 用 Turbopack 跑测试 / 双打包器对比

# 用 -turbo 系列脚本走 Turbopack
pnpm test-dev-turbo test/e2e/app-dir/app/

# 一次测试同时跑 Turbopack 与 Webpack:用 Jest 的 --projects 指定两份 jest 配置
pnpm test-dev test/e2e/app-dir/app/ --projects jest.config.*

这与仓库根目录同时存在 jest.config.jsjest.config.turbopack.js 的结构相对应。

4.5 Deploy 测试

Deploy 测试验证 Next.js 部署到 Vercel 后的行为,属于 e2e 套件的一部分,针对真实 Vercel 部署运行。它们自动在 canary 上触发;PR 中则只在修改或新建了对应测试文件时触发,也可以通过 Actions UI 手动指向自己的分支触发(需要自定义 tarball)。

本地对指定 commit 跑 deploy 测试:

NEXT_TEST_VERSION=https://vercel-packages.vercel.app/next/commits/<commitSha>/next pnpm test-deploy <path-to-test>

例如对 commit abc123 测试 test/e2e/app-dir/actions/

NEXT_TEST_VERSION=https://vercel-packages.vercel.app/next/commits/abc123/next pnpm test-deploy test/e2e/app-dir/actions/

该命令会下载指定 commit 预构建的 Next.js tarball 并对它跑 deploy 测试。已有部署 URL 时可用 NEXT_TEST_DEPLOY_URL 跳过部署步骤。

五、在自己的应用中验证本地 Next.js

这是贡献过程中最实用的部分,文档分散在 developing.mddeveloping-using-local-app.md

由于 Turbopack 不支持指向工作区目录之外的符号链接,pnpm linkfile: 直接导入往往不够用。官方推荐的替代方案是:把本地 Next.js 打成 tarball,再通过 pnpm overrides 注入到测试应用

# 生成 tarball 并注入测试项目
pnpm pack-next --tar && pnpm unpack-next path/to/project

# 跳过 monorepo 内部的 pnpm i 和 pnpm build(例如正在跑 pnpm dev 时)
pnpm pack-next --no-js-build --tar && pnpm unpack-next path/to/project

# 在项目目录内生成带可部署相对 file: 引用的 tarball
pnpm pack-next --project path/to/project --deployable-tar

# 不走 tarball 直接打补丁(前提:已添加 pack-next 生成的 overrides)
pnpm patch-next path/to/project
pnpm patch-next --no-js-build path/to/project

pnpm pack-next 还接受 -- 分隔符向 napi CLI 透传参数(如 pnpm pack-next --project ~/my-project/ -- --release),--project 会自动定位并修改父级 workspace 的 package.json,对 npm 和 pnpm 有效,bun 与 yarn 存在已知问题。在 Linux 上,为避免超过 pnpm 已知有问题的 2 GiB 上限,生成的是 stripped 的 @next/swc 二进制;可用 --compress objcopy-zstd 覆盖(更慢但保留 debuginfo)。tarball 写入仓库根目录的 tarballs 目录。

5.1 在 monorepo 内部开发

  • 无依赖的小应用:直接在 monorepo 里建一个目录(如 dev-app),运行 pnpm next ./dev-app 即可,甚至不需要 package.json
  • 有依赖的既有应用:把应用挪进 monorepo,然后运行 pnpm next-with-deps ./app-path-in-monorepo
  • 也可以让 pnpm dev 常驻另一个终端,边改 Next.js 边测试(部分改动需要重新执行 pnpm next-with-depspnpm next)。

5.2 作为本地依赖引入

  1. 在 monorepo 中后台运行 pnpm dev

  2. 在应用根目录执行(把 nextreactreact-dom 全部指向 monorepo 版本):

    pnpm add ./path/to/next.js/{packages/next,node_modules/{react,react-dom}}
    
  3. 正常启动应用。此时 Next.js 来自本地编译产物而非 npm registry。

排障:若 pnpm dev 时报 Failed to load SWC binary,在应用 package.json 中加入对应平台的 SWC 二进制依赖(canary 版本)再试:

{
  "optionalDependencies": {
    "@next/swc-linux-x64-gnu": "canary",
    "@next/swc-win32-x64-msvc": "canary",
    "@next/swc-darwin-x64": "canary",
    "@next/swc-darwin-arm64": "canary"
  }
}

六、文档与错误链接

  • 文档贡献:Next.js 的文档位于仓库 docs/ 目录(如 docs/01-appdocs/02-pages),文档贡献说明 指向官方 Docs Contribution Guide。琐碎的文字勘误类改动在这一目录是被明确欢迎的贡献入口;

  • 添加错误链接adding-error-links.md 说明了 Next.js 的"短消息 + 详细文档"机制——控制台里的警告/错误保持简短,把完整说明和解决步骤放到文档。新增警告或错误时应附带这种链接,步骤为:

    1. 运行 pnpm new-error,自动创建错误文档并更新 manifest;
    2. 命令结束时会给出生成的错误 URL,把它加到对应的错误抛出处。

仓库 errors/ 目录中可以看到大量这类错误文档(如 swc-disabled.mdxinvalid-redirect-gssp.mdx 等),与该机制一一对应。

七、代码风格:Lint 与格式化

规范见 linting.md。仓库使用 ESLint、Prettier 与 alex(检查包容性用语)对代码与文档统一检查:

pnpm lint       # 全量检查
pnpm lint-fix   # 自动修复 ESLint 与 Prettier 问题(部分规则需手工修改)

package.jsonlint 脚本可以看出它的覆盖面:类型检查(test-typeslint-typescript)、Prettier 校验、ESLint、ast-grep 规则、语言检查(alex)以及 check-unused-turbo-tasks。相关配置:

指南建议在 VS Code 中安装对应插件实现保存即检查/格式化,并对 alex 的告警按提示修改措辞。

八、Issue 分诊(Triaging)

面向维护者的分诊流程见 triaging.md,理解它对贡献者同样重要,因为它决定了你的 issue/PR 会怎样被处理:

  • Issue 会被打上一级标签之一:bug(Next.js 本身的缺陷)、documentation(文档问题)、examplesexamples/ 目录中示例的问题);
  • 自动化分诊:bug report 若缺少有效复现,会被自动关闭并附上引导评论和 invalid link 标签;填写模板中"Which area(s) are affected?"会自动加对应标签;
  • 人工分诊标签(各附引导评论):
    • please add a complete reproduction:复现不足,2 天无响应自动关闭;
    • please verify canary:未用 next@canary 验证,canary 相当于公开 beta,14 天无响应自动关闭;
    • please simplify reproduction:复现过于复杂,14 天无响应自动关闭;
    • good first issue:标记为新手友好;
    • resolved:假定新版已修复,14 天内可按引导请求重开;
  • 已验证的 issue 会获得 linear: next / linear: turbopack / linear: docs 标签并进入维护者跟踪,确认过的 issue 不会因久未活动而过期关闭;
  • 关闭后的处理:未锁定 issue 被维护者关闭时会附评论,说明 14 天内如何请求重开(需包含 Reopen: 前缀与理由);所有已关闭的 PR/Issue 在 2 周无活动后会被锁定,重开窗口结束后应开新 issue 并引用旧 issue。

九、发布通道:stable 与 canary

发布流程见 release-channels-publishing.md

  • stablenpm install next 装到的就是它,遵循语义化版本,按固定节奏发布。维护者用 pnpm publish-stable 发布,命令会询问发布 majorminor 还是 patch
  • canary:需显式 npm install next@canary 安装。基于 canary 分支提前发布,承载所有等待进入 stable 的变更,用于在真实应用中试跑最新特性与修复。维护者用 pnpm publish-canary 发布,版本号自动在上一版本上递增。

从源码结构看,canary 既是开发分支又是 npm 标签,这也解释了前文"所有 PR 面向 canary"与测试体系中 canary 版本验证要求(please verify canary)之间的关联。

十、PR 描述与 Turbopack 追踪

  • PR 描述pull-request-descriptions.md 要求使用仓库默认 PR 模板,写清 PR 目的并链接相关 issue;

  • Turbopack 追踪tracing.md 介绍 turbo-tasks 的 tracing 能力,可在 Next.js 中用 NEXT_TURBOPACK_TRACING 环境变量开启:

    • 1overview:基础用户级追踪(官方发布的 Next.js 只内置这一档,即 info 级);
    • next:在其上叠加 Next.js 自有 crate 的 debug/trace 日志;
    • turbopack:再叠加 Turbopack 自有 crate 的 debug/trace 日志;
    • turbo-tasks:进一步追踪每一个 Turbo-Engine 函数执行;
    • 也支持 tracing_subscriber::filter::EnvFilter 的任意 directives 语法。

    开启后 Next.js 会写出二进制的 .next-profiles/trace-turbopack.bin 文件,可用 turbo-trace-viewer 可视化(trace server 连接 57475 端口的 WebSocket)。启动方式:

    cargo run --bin turbo-trace-server --release -- /path/to/your/trace-turbopack.bin
    # 或
    pnpm next internal trace .next-profiles/trace-turbopack.bin
    

    追踪查看器提供聚合/单个 span、按发生顺序/按数值排序、bottom-up(self 值)等视图,以及 Duration、分配/释放/常驻内存等度量模式。注意:更详细的追踪档位需要自建 Next.js(见第二章的 pnpm dev / pnpm swc-build-native 流程);trace server 务必用 --release 构建,性能差距可达 10 倍。

小结:一张快速参考表

场景 命令
克隆仓库 git clone --filter=blob:none --single-branch
安装依赖 pnpm install
监听式开发 pnpm dev
构建全部(JS + 类型) pnpm build / 单独类型 pnpm types
构建 Rust 绑定 pnpm swc-build-native [--release] / pnpm build-all
跑测试(prod / dev / 调试 / turbo) pnpm test-start / pnpm test-dev / pnpm testonly-start / pnpm test-dev-turbo(后接测试目录)
新建测试 / 新建错误文档 pnpm new-test / pnpm new-error
本地验证到真实应用 pnpm pack-next --tar && pnpm unpack-next <proj>pnpm patch-next <proj>
在 monorepo 内跑应用 pnpm next ./dev-app / pnpm next-with-deps ./app
Lint 与自动修复 pnpm lint / pnpm lint-fix
清理磁盘 pnpm sweep / pnpm clean

整体来看,Next.js 的贡献体系可以概括为:canary 分支 + pnpm/Turborepo 工作流 + 隔离式 e2e 测试 + tarball 注入验证 + 签名提交 的组合。遵循 contributing.md 及其子文档(repository/core/turbopack/docs/)给出的边界——先讨论后实现、避免琐碎 PR、提交必须签名——就能与 CI 和维护者的审查流程顺畅对齐。

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