Next.js 发布通道解析:stable 与 canary 双通道机制、dist-tag 策略与发布脚本源码剖析
Next.js 通过 stable 与 canary 两条发布通道向 npm 分发版本:stable 是绝大多数用户 npm install next 得到的正式版本,canary 则是基于 canary 分支自动递增、用于在真实应用中提前验证新特性与修复的预发布版本。本文以官方发布流程文档为骨架,结合 scripts/ 下的发布脚本与 lerna.json 配置,完整讲清两条通道的语义差异、版本号生成规则、npm dist-tag 映射逻辑,以及一次完整发布背后 native 二进制、wasm 包与 GitHub Release 的联动细节。
两条发布通道:stable 与 canary
Next.js 官方维护两个发布通道:
- stable(稳定通道):用户执行
npm install next时安装的版本,是绝大多数 Next.js 用户使用的版本。该通道按固定节奏发布,并遵循语义化版本(semantic versioning)规范。 - canary(金丝雀通道):需要用户显式安装——
npm install next@canary。该通道基于canary分支提前发布,承载所有等待进入 stable 通道的变更,用于在真实世界应用中测试最新特性与 bugfix。用户定期升级到next@canary可以提前检查尚未发布的变更是否影响自己的应用。
两条通道的核心区别在于版本号的确定方式与面向的用户群体:
| 维度 | stable | canary |
|---|---|---|
| 安装方式 | npm install next(默认 latest) |
npm install next@canary |
| 版本号来源 | 语义化版本,发布时由维护者选择 bump 级别 | 自动从上一版本递增 |
| 代码来源 | 稳定的发布基线 | canary 分支 |
| 定位 | 生产可用、固定节奏发布 | 真实环境预演最新变更 |
仓库中的版本载体:lerna.json
两条通道的版本号都保存在仓库根目录的 lerna.json 中。该文件同时揭示了发布流程的几个关键约束:
{
"npmClient": "pnpm",
"packages": ["packages/*"],
"command": {
"version": { "exact": true },
"publish": {
"npmClient": "npm",
"allowBranch": ["canary"],
"registry": "https://registry.npmjs.org/"
}
},
"version": "16.4.0-canary.14"
}
"version": "16.4.0-canary.14":当前canary分支的版本。可以推断,每次 canary 发布都会在16.4.0-canary.x的 prerelease 序号上递增,这正是文档中“canary 版本号自动决定”的落点;"version": { "exact": true }:所有包使用精确版本号(不带^/~),保证多包仓库内依赖版本严格锁定;"publish": { "allowBranch": ["canary"] }:Lerna 默认只允许在canary分支执行发布,这是通道与分支绑定的直接证据;- 发布目标固定为 npm 官方 registry。
stable 通道的发布流程
文档指出,仓库维护者通过 pnpm publish-stable 发布新的 stable 版本,命令会询问要发布 major、minor 还是 patch。
版本 bump 的执行:lerna version
从源码结构看,这套流程当前由 scripts/start-release.js 驱动。该脚本接受 --release-type(stable、canary、release-candidate、beta、preview)与 --semver-type(patch、minor、major)两组参数,其中:
- stable 通道必须显式指定
--semver-type,对应文档中“命令询问 major/minor/patch”的交互——脚本在第 131 行校验:canary和preview自动推导版本,不需要 semver type,其余通道必须传入合法的patch/minor/major之一; - 参数映射到 Lerna 子命令(scripts/start-release.js):
- stable 直接传
patch/minor/major; - canary/rc/beta 映射为
prerelease/premajor/preminor并附加--preid canary|rc|beta; - 统一追加
--force-publish -y --no-push:强制发布(即使无变更提交)、跳过确认、版本提交后不立即推送,改由脚本统一创建签名的发布提交。
- stable 直接传
随后脚本调用 createGitHubReleaseCommit 通过 GitHub API 创建提交与 v<version> 标签(scripts/release-github-api.js)。脚本还支持 --dry-run:此时所有 GitHub API 调用走一个 mock client(仅打印 method/path/body),完整演练签名、打标签、推送、创建 Release 的流程而不产生任何远端副作用(scripts/start-release.js)。
stable 发布到 npm 的 dist-tag:latest 与 backport
发布到 npm 时,dist-tag 的决定逻辑集中在 scripts/publish-release.js。核心规则(第 32-36 行):
const prereleaseChannel = parsedVersion.prerelease[0] // 如 'canary'、'rc'、'beta'
const isPrerelease = prereleaseChannel != null
let npmDistTag = isPrerelease ? String(prereleaseChannel) : 'latest'
即:带 prerelease 后缀的版本打对应标签(canary、rc、beta…),纯版本号则打 latest——这解释了为什么 npm install next 能始终拿到 stable。
一个容易被忽略的细节是 backport(回移)保护(scripts/publish-release.js):如果当前待发布版本低于 npm 上已有的 latest 版本(典型的旧版本线安全回移场景),由于 npm 发布时默认会把 latest 指向新包、可能让用户装到比线上更旧的版本,脚本会显式把 dist-tag 改为 backport 而非 latest。仓库中还有配套的 scripts/check-backport-canary-release.js 与 scripts/normalize-version-bump.js 处理回移发布时版本号与 canary 线的一致性,说明回移是被正式支持、且有专门校验的发布场景。
canary 通道的发布流程
文档说明维护者通过 pnpm publish-canary 发布 canary,版本号自动从上一版本递增。结合源码可以完整还原这一“自动决定”的机制:
lerna version prerelease --preid canary在 lerna.json 当前版本(如16.4.0-canary.14)基础上递增 prerelease 序号(如16.4.0-canary.15);--force-publish保证即使当次只有一个改动也能产出新版本;- scripts/publish-release.js 读取新版本,
prerelease[0]为canary,故 dist-tag 直接取canary,用户侧npm install next@canary即可安装; - canary 等预发布通道在 GitHub 上创建的是 draft release(scripts/start-release.js 中对
canary/rc/beta/preview调用createGitHubRelease),待 npm 发布完成后,scripts/publish-release.js 轮询 GitHub releases 列表找到v<version>标签,PATCH{"draft": false}将其转为正式发布。
canary 与 stable 的衔接点在于:canary 分支累积所有待发变更(lerna.json 中 allowBranch: ["canary"] 限定发布只从该分支发起),待其被认为足够稳定后,再走 stable 通道按 semver 规则正式放出。仓库中 scripts/check-is-release.js 用于识别一个提交是否为“发布提交”,scripts/create-release-branch.js 与 scripts/normalize-version-bump.js 则负责发布分支的创建与版本号规范化——.github/workflows/create_release_branch.yml、.github/workflows/trigger_release.yml 等 CI 工作流把这些脚本串成了自动化的发布流水线。
一次完整发布到底发布了什么
阅读 scripts/publish-release.js 可以看清 Next.js 发布不止是发一个 next 包,而是一组联动的 npm 包,全部使用同一个 dist-tag:
- 平台原生二进制:遍历 crates/next-napi-bindings/npm 下的各平台目录(linux-x64、darwin-arm64 等),把
packages/next-swc/native/下编译好的next-swc.<platform>.node复制进去,同步版本号后逐个发布为@next/swc-<platform>包; - wasm 包:把 crates/wasm 下的
pkg-web/pkg-nodejs改名发布为@next/swc-wasm-web与@next/swc-wasm-nodejs;wasm 发布失败默认不中止整个发布(可设BAIL_ON_NATIVE_WASM_PUBLISH_FAILURE=true强制中止),而原生包任一失败则process.exit(1); - 主包
next:把各平台@next/swc-<platform>写进 packages/next/package.json 的optionalDependencies,并删除devDependencies、taskr、scripts字段以压缩 npm 上该包的历史元数据体积(源码注释提到上限 100MB、当前约 30MB),随后pnpm --filter './packages/**' publish --recursive --access public --no-git-checks --ignore-scripts --tag <dist-tag>一次性发布整个packages/*工作区(对应 lerna.json 中"packages": ["packages/*"]); - 可靠性保障:每次 npm publish 通过信号量并发限制为 2,失败后按 15 秒间隔重试,最多 4 次;若错误信息包含 “cannot publish over the previously published versions”(同版本号重复发布)则直接忽略,使发布流程具备幂等性(scripts/publish-release.js)。
延伸:rc、beta 与 preview 通道
从 scripts/start-release.js 支持的 --release-type 取值可以看出,实际发布矩阵比文档列举的两条通道更宽:
- release-candidate(
--preid rc)与 beta(--preid beta):在 stable 正式号发布前的预验证通道,版本号分别为x.y.z-rc.n、x.y.z-beta.n,dist-tag 即rc/beta,用户可通过npm install next@rc等显式安装; - preview:从
canary临时切出的特殊通道。它没有自己的版本计数器分支,版本由脚本从max(canary 基线, npm 上已发布的 next@preview)推导(scripts/start-release.js);发布后脚本会git revert掉 Lerna 的版本 bump 提交,让canary线继续自增,preview 标签仍指向 bump 提交(scripts/start-release.js)。preview 还会配合 scripts/create-preview-tarballs.js、scripts/upload-preview-tarballs.js 与 scripts/set-preview-version.js 生成并分发预览 tarball,供 CI 与集成测试使用(对应 test/deploy-tests-manifest.json 等部署测试)。
这些通道共享同一套 dist-tag 规则:版本号的 prerelease 段第一个 token 就是 npm 标签名。
总结:通道、分支与标签的对应关系
| 发布动作 | 分支/基线 | 版本号形态 | npm dist-tag | GitHub Release |
|---|---|---|---|---|
| stable | 发布基线 | 16.x.y(semver bump) |
latest(回移时为 backport) |
正式 |
| canary | canary 分支 |
16.x.y-canary.n(自动 +1) |
canary |
draft → 转正 |
| rc / beta | 发布流程 | 16.x.y-rc.n / 16.x.y-beta.n |
rc / beta |
draft → 转正 |
| preview | 从 canary 临时切出 | 16.x.y-preview.n(推导后 revert canary 线) |
preview |
draft → 转正 |
对使用者而言,记忆点很简单:npm install next 走 stable,npm install next@canary(或 next@rc、next@beta)做提前验证;对维护者而言,发布是一个“Lerna 定版 → GitHub 签名打标签 → npm 多包联动发布 → draft release 转正”的确定性流水线,全部逻辑沉淀在 scripts/start-release.js、scripts/publish-release.js 与 lerna.json 中,可按需阅读源码深入。
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 StartedRust0622
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