团队里那台跑了四年的Jenkins服务器最近频繁报警:磁盘满了、插件更新后构建脚本失效、节点机偶尔掉线。更麻烦的是,新同事入职后要花好几天才能看懂Jenkinsfile和各种插件的组合逻辑。如果把代码仓库放在GitHub上,为什么不直接用GitHub Actions,把CI/CD和代码托管放在同一个平台里?这篇文章记录了一次React项目从Jenkins迁移到GitHub Actions的完整过程,包括两者设计理念的差异、workflow文件怎么写、构建缓存怎么配,以及迁移时最容易忽略的几个问题。

一、先搞清楚:Jenkins和GitHub Actions的核心差异
Jenkins是典型的服务端CI工具,所有构建都在你自己维护的服务器或节点机上执行,自由度极高,但代价是你要负责操作系统升级、插件兼容性、安全补丁这些与业务无关的运维工作。GitHub Actions则是把执行器托管在GitHub的云端,仓库根目录下的.github/workflows目录中的YAML文件就是流水线的定义,代码与流水线配置在同一个代码库里,评审流水线改动和评审业务代码的流程完全一致。
两者在概念上有一一对应的关系。Jenkins的Agent对应Actions的runner,stage对应job,step基本同名,Jenkins的credential对应Actions的secrets。理解了这个映射关系,迁移时的思路就清晰了:把Jenkinsfile里的每个stage拆成YAML里的一个job或一组step。另外,Actions的触发器写在on字段里,支持push、pull_request、schedule、workflow_dispatch等多种事件,比Jenkins的webhook配置直观不少。
还有一个容易被忽略的收益:Actions的公有仓库完全免费,私有仓库每月也有一定额度的免费分钟数。对于中小团队的前端项目,这笔账算下来往往能省掉一台构建服务器的成本。
二、编写第一个React构建workflow
假设原来的Jenkinsfile包含拉代码、安装依赖、跑测试、构建、部署五个阶段,下面是对应的workflow文件。先看一个可以直接使用的完整示例:
name: React CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 拉取代码
uses: actions/checkout@v4
- name: 配置Node环境
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: 安装依赖
run: npm ci
- name: 执行单元测试
run: npm test -- --coverage
- name: 构建
run: npm run build
env:
CI: false
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/几个细节值得展开说。npm ci比npm install更适合CI环境,它会严格按照package-lock.json安装,速度更快且结果可复现。setup-node的cache: npm参数会自动缓存npm的下载目录,第二次构建开始安装依赖的时间通常能缩短一半以上。如果项目用的是pnpm或yarn,改成对应的值即可。
构建React时经常遇到把warning当error处理的ESLint检查,Jenkins时代大家习惯在命令里设置环境变量绕过,Actions里同样可以通过env块解决,但更推荐的做法是修掉真正的warning,把CI: false这种应急手段只作为过渡期方案保留。
三、缓存与依赖安装的进阶优化
React项目构建慢的瓶颈几乎都在依赖安装和webpack缓存这两块。除了上面提到的npm缓存,还应该缓存构建工具自身的缓存目录。以webpack为例,它在二次构建时会生成node_modules/.cache,把这个目录缓存起来效果非常明显:
- name: 缓存构建产物
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules/.cache
key: ${{ runner.os }}-build-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-key里拼接了lock文件的哈希值,依赖一变缓存自动失效;restore-keys作为兜底,即使精确匹配失败也能用旧的缓存部分恢复。对于使用Vite的项目,可以把node_modules/.cache换成node_modules/.vite,思路完全一致。
如果你的仓库开启了 Dependabot 或者分支很多,还可以考虑在workflow里加并发控制,避免同一个分支被重复触发多次构建浪费额度:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true这段配置放在workflow顶层,同一分支的新构建会自动取消还在排队或执行中的旧构建,对频繁推送代码的开发者来说体验会好很多。
四、部署与密钥管理的迁移要点
部署环节是迁移中最容易出事的部分。Jenkins里通常把服务器SSH私钥放在全局credential里,迁移到Actions后要把这些敏感信息搬到仓库的Settings、Secrets and variables、Actions中。注意secrets的名字全仓库唯一,建议统一命名规范,比如DEPLOY_SSH_KEY、DEPLOY_HOST,方便后续维护。
部署到自有服务器时,用appleboy/ssh-action这个成熟的第三方action就能搞定:
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: 下载构建产物
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: 通过SSH部署到服务器
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
rm -rf /var/www/react-app/*
scp 前请改用rsync或直接在服务器端解包
# 实际项目中通常先打包再传输
echo "部署完成"这里演示的是思路,实际操作中更常见的做法是把dist目录打成压缩包上传再解压,避免逐个传小文件。needs: build保证了部署任务在构建成功后才执行,if条件则确保只有main分支的推送才触发部署,pull request只跑测试不发布,这个行为和大多数团队的Jenkins配置是一致的。
如果目标是部署到GitHub Pages、Vercel或OSS这类平台,直接搜对应官方提供的action即可,配置比SSH方案更简单,一般只需要一个token。
五、迁移过程中的常见坑
第一个坑是secrets在fork的PR里不可见。这是GitHub的安全设计,外部贡献者提的pull request拿不到你的密钥,如果流水线里有依赖密钥的步骤会直接失败。解决办法是把这类步骤加上if: github.event_name == 'push'的条件,PR里只跑纯构建和测试。
第二个坑是大小写敏感。Linux runner的文件系统区分大小写,而不少在Mac上开发的React项目里import路径大小写写错了也能本地跑通,一上CI就报模块找不到。这种问题迁移初期高发,逐个修正import路径即可,顺便也把隐患消除了。
第三个坑是免费额度消耗。私有仓库每月有免费分钟数,跑得多了要注意计费页面。用并发控制、精简测试范围、合理利用缓存都能有效降低消耗。极端情况下可以给workflow加workflow_dispatch手动触发,把一些耗时的构建改成按需执行。
整体迁移下来,我们那个React项目的流水线配置从两百多行Jenkinsfile加上十几个插件依赖,收敛到了一个两百行以内的YAML文件,服务器下线,新同事看一遍workflow就能上手改。如果你的团队也在为Jenkins的维护成本头疼,这波迁移值得排进日程。
ReactGitHub ActionsJenkins迁移修改时间:2026-09-09 07:18:40