大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
构建 Next.js platform
这一课你将能做到
- 写出完整的 platform job:
setup-node+npm ci+lint/test/build,工作目录落在platform/ - 处理 Next 构建期的环境变量(如
NEXT_PUBLIC_API_BASE),避免「本机有.env.local、CI 没有」导致的假红 - 说清 CI 验证的是「可生产构建」,与本地
next dev开发流的区别与分工
为什么要学
本站的 platform 是 npm + Next.js。前端的失败形态和 Java 不同:lint 违规、类型错误、next build 编译期才暴露的问题(导入错路径、页面级类型不匹配、SSR 兼容问题),在 dev 热更新下常被忽略。
CI 里跑一遍 lint + test + build,等于强制「合并前必须能产出生产包」。对 monorepo 而言,它与 backend job 并行,组成完整门禁。
本课给出可粘贴的 job 骨架,并点出 env、lockfile、缓存三个最常见卡点。
先建立图像
job: platform(ubuntu-latest)
checkout
setup-node 20 + cache npm(lock: platform/package-lock.json)
working-directory: platform
npm ci ← 按锁文件干净安装
npm run lint
npm test
npm run build ← 生产构建,最接近「能不能上线」
人话:把 CI 当成一位从不打开 IDE 的前端同事——只认 package-lock.json 与 npm run 脚本。你本地靠 .env.local 的侥幸,他那里没有。
| 脚本 | 作用 | CI 是否建议 |
|---|---|---|
npm ci | 按锁文件干净安装 | 是(优于随手 npm i) |
lint | 风格与部分错误 | 是 |
test | 单元/组件测 | 是(有则跑) |
build | 生产编译 | 是 |
dev | 开发服务器 | 否(不适合当门禁) |
跟着做
步骤 1:本地先对齐脚本
cd platform
npm ci
npm run lint
npm test
npm run build
任一失败先本地修。同时确认 package.json 里的脚本名(lint/test/build)与即将写入的 YAML 一致——脚本名对不上是入门常见乌龙。
步骤 2:写入 platform job
jobs:
platform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: platform
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 安装 Node 20
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
# monorepo:锁文件不在仓库根
cache-dependency-path: platform/package-lock.json
- name: 干净安装依赖
run: npm ci
- name: Lint
run: npm run lint
- name: 测试
run: npm test
- name: 生产构建
env:
# CI 无 .env.local 时给构建期占位,避免缺变量直接炸
NEXT_PUBLIC_API_BASE: http://localhost:8080
run: npm run build
与 backend 放同一 ci.yml、不写 needs,即可并行。
步骤 3:验证锁文件纪律
临时把 npm ci 改成 npm install 对比一次(看完改回):npm install 会容忍 lock 与 package.json 漂移,npm ci 则直接报错——这正是它的优点。笔记写:CI 用 npm ci 保卫锁文件,不被「随手 install」漂版本。
步骤 4:故意制造 lint/build 失败
引入一条 eslint 违规(如未使用变量)→ push → 看红在哪个 step;再弄坏一个页面导入,看红移到 build。修回后确认全绿。练习「点开失败 step 直接读日志」,而不是从头翻十分钟下载日志。
步骤 5:认识构建产物(为 Docker 课铺路)
若项目启用 Next 的 standalone 输出,构建后常见:
platform/.next/standalone ← 可独立运行的 server.js 及最小 node_modules
platform/.next/static ← 静态资源
本站打包脚本与 Dockerfile 会消费这些产物。CI 本课以「build 成功」为门禁;要不要把 .next 传成 artifact 看下游需求——若 Docker 构建在 CI 里重跑 next build(多阶段构建),则无需传递。
步骤 6:paths 过滤(与第 11 课呼应)
仅改 backend/** 时,可让 platform job 跳过(paths / paths-ignore 或 job 级条件表达式)。入门阶段先两边都跑保证简单;分钟数吃紧再优化。
讲清楚要点
- 因为生产环境跑的是 build 产物,所以 CI 必须跑
npm run build——大量错误只在 build 编译期出现,dev永远发现不了。 - 因为依赖版本应可复现,所以提交
package-lock.json并用npm ci——与本地「能跑就行」的惯性对抗。 - 因为 Next 构建期读
NEXT_PUBLIC_*,所以 CI 要显式提供所需 env——不要假设存在.env.local(它通常被 gitignore)。 - 因为 monorepo 锁文件在子目录,所以
cache-dependency-path与working-directory都要指对——根目录 npm 会装错地方。 - 因为 lint/test/build 失败原因不同,所以拆成多个 step——一条
&&糊到底,日志没法定位。 - 因为前端 CI 与后端 CI 目标对称,所以与 backend job 并列组成仓库门禁——一边绿不够。
你可能会懵的地方
-
「本机 build 过、CI 报缺少 env?」 本机有
.env*.local(不进 Git)。把构建真正需要的变量写进 workflowenv,或在仓库维护.env.example文档化。注意:NEXT_PUBLIC_*会被打进客户端产物,所以不要放敏感值。 -
「
npm ci报 lock 不同步?」 在platform/重新生成并提交 lock(npm install一次后提交变更),不要在 CI 里用npm install掩盖——那等于放弃版本锁定。 -
「只要 lint 绿是不是就够了?」 不够。类型错误、非法导入、服务端组件约束等都在
next build才爆。门禁必须包含 build。 -
「Node 18 还是 20?」 与项目
engines字段/文档对齐;主路径先钉一版(如 20),需要兼容验证时用 matrixnode: [20, 22]。 -
「构建时内存不够(OOM)?」 Next 大项目偶见堆内存不足,可加
env: NODE_OPTIONS: --max-old-space-size=4096。托管 runner 7GB 内存,够绝大多数场景;仍 OOM 说明构建本身要瘦身(依赖、图片、未用代码)。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
CI 用 npm install 且不提交 lock | 版本漂移,复现困难 | 提交 lock + npm ci |
| 只跑 lint 不 build | 假绿,上线才炸 | lint + test + build |
| 在仓库根执行 npm 脚本 | 找错 package.json | working-directory: platform |
依赖本机 .env.local | CI 缺变量假红 | workflow env 或 .env.example |
把敏感值放进 NEXT_PUBLIC_* | 打进客户端产物公开可见 | 敏感值走服务端 env / secrets |
卡点急救
npm ci报lock file ... does not satisfy:lock 与package.json不同步。本地在platform/跑一次npm install,提交更新的package-lock.json;顺带检查是不是有人手改了 package.json 没装。- build 报
NEXT_PUBLIC_XXX is not defined或行为异常:列出项目构建期真正读取的公开变量,全部写进 build step 的env;区分构建期(NEXT_PUBLIC_*)与运行期(服务端process.env)变量,前者必须 build 时存在。 - 构建耗时从 2 分钟涨到 10 分钟:先看是否缓存 miss(lock 变更属正常);再看是否新增了巨型依赖或开启了未用的分析/追踪;必要时把 lint 与 build 拆成并行 job 缩短墙钟。
面试常问
-
npm ci和npm install有什么区别?CI 里为什么用 ci?npm install按 package.json 解析并可能更新 lock;npm ci严格按 lock 全新安装,lock 与 manifest 不一致时直接失败。CI 要的是可复现:同一 lock 永远装出同一依赖树,还能顺带把「lock 忘了提交」这种问题在 PR 阶段暴露。 -
Next.js 的
NEXT_PUBLIC_*环境变量有什么特殊性?对 CI 有什么要求? 以NEXT_PUBLIC_开头的变量在构建时被内联进客户端产物,所以必须在next build时可用且会随构建固化——运行时改环境变量不生效,敏感值绝不能放这里。因此 CI 的 build step 要显式提供这些变量(或约定默认值),并且构建用的值要与部署环境约定一致。 -
前端 CI 门禁应该包含哪些环节?为什么? 依赖安装(npm ci 保可复现)→ lint(秒级,便宜检查放前面)→ 测试(单元/组件)→ 生产构建(catch 类型与编译期错误)。顺序遵循「便宜的先跑」:lint 3 秒挂掉就不浪费后面几分钟的 build。如果产出镜像,build 可挪进 Docker 多阶段构建,CI 门禁至少保证能产出可运行包。
自测三问
- 为什么 CI 推荐
npm ci而不是npm install? - platform job 建议的四个命令阶段是什么?顺序为什么这样排?
- monorepo 里
cache-dependency-path应指向哪个文件?
小结一句话
npm ci + lint + test + build(含必要的构建期 env)是本站 Next.js 前端 CI 四件套。
延伸
- Building and testing Node.js
- Next.js — Production Optimization
- 下一课:构建推送镜像——把两端产物变成可部署的 Docker 镜像。