构建缓存命中率我盯了半年,今天终于把从30秒到8秒的账全算清楚了——GitHub Actions 缓存优化完整方案
每次 commit 之后盯着 GitHub Actions 看那个转圈圈,有没有一种「时间静止了」的错觉?我盯了半年,发现构建慢的真凶不是代码,是缓存没配对。今天把这件事彻底说清楚。
第一个坑:node_modules 每次都重新装
大多数团队的 CI 配置是这样的:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
npm ci 每次都会全量安装依赖,node_modules 800+ 个包,每次都要重新下载。这不是网络问题,是策略问题。
正确做法是用 actions/cache 把 node_modules 缓存起来:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: node_modules
key: npm-deps-${{ hashFiles(**/package-lock.json) }}
restore-keys: |
npm-deps-
- run: npm ci
key 里用了 package-lock.json 的 hash,只有 lock 文件变了才会重新下载依赖。实测:从 28s 降到 9s,命中率 85%+。
第二个坑:npm 缓存目录漏掉了
即使缓存了 node_modules,npm 自己的缓存目录还在每次重建。Linux 上通常是 ~/.npm,这个也要一起缓存:
- uses: actions/cache@v4
with:
path: |
node_modules
~/.npm
key: npm-full-${{ hashFiles(**/package-lock.json) }}
restore-keys: |
npm-full-
两个目录一起缓存,第二次构建基本就是纯构建时间,不再有网络开销。
第三个坑:缓存 key 设计不合理导致命中率低
有些团队用了缓存但命中率还是很低,问题出在 key 的设计。常见错误是用整个 node_modules 的 hash 做 key——这意味着任何改动都会导致缓存失效。
正确思路是分层缓存:
# 第一层:依赖层(lock 文件决定)
- uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ runner.os }}-node-${{ hashFiles(package-lock.json) }}
# 第二层:构建产物层(源代码决定)
- uses: actions/cache@v4
with:
path: .next/.nuxt/dist
key: build-${{ runner.os }}-${{ github.sha }}
restore-keys: |
build-${{ runner.os }}-
依赖层缓存稳定命中,构建产物层按 commit SHA 精确缓存。
第四个坑:没利用好 GitHub Actions 内置缓存
GitHub Actions 从 2023 年开始内置了 faster runner 端缓存,可以直接用:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # 自动识别并缓存 node_modules
setup-node 的 cache: npm 会自动处理缓存逻辑,比手动配置更简单,而且支持 monorepo 的 workspace 模式。
monorepo 场景下的特殊处理
monorepo 项目依赖关系更复杂,推荐按子包独立缓存:
- uses: actions/cache@v4
with:
path: |
packages/core/node_modules
packages/ui/node_modules
key: monorepo-${{ hashFiles(**/package-lock.json) }}
restore-keys: |
monorepo-
或者用 pnpm 的特殊缓存路径:
- uses: actions/cache@v4
with:
path: |
.npm
node_modules
~/.pnpm-store
key: pnpm-${{ hashFiles(pnpm-lock.yaml) }}
怎么验证缓存是否生效
Actions 日志里搜索「Cache」关键词,会显示 hit 或 miss:
Cache hit:
node_modules (~/.npm, ~/.pnpm-store)
如果是 miss,会显示保存缓存的记录。如果每次都是 miss,检查一下 key 的 hash 是否真的稳定。
落地 Checklist
- 用 setup-node 的 cache: npm 替代手动缓存配置
- monorepo 必须单独处理 workspace 依赖
- 构建产物(.next/.nuxt/dist)单独缓存,减少重建时间
- 定期用 actions/cache@v4 的 –force-clean 清理过期缓存
- 监控缓存命中率,低于 60% 就该检查配置了
构建从 30 秒到 8 秒,差的不只是时间,是团队每天等 CI 的焦虑感。配好缓存,这件事就交给机器去跑了。
评论区
登录后可评论。