【実務・中級編】CI/CDでキャッシュを最適化せよ!GitHub Actionsでのnpm/yarn/pnpmキャッシュ保存戦略 – ビルド・パッケージ管理ツール生産性向上バイブル

CI/CDの「ビルド時間」はエンジニアの寿命である。GitHub Actionsキャッシュ戦略の深淵

テックリードとして現場を見渡すと、多くのプロジェクトで「とりあえず動く」だけのCI設定が、日々数千回の無駄なビルド時間を消費している光景を目にする。

`npm install` や `pnpm install` に毎回2〜3分費やしているなら、それは技術的負債だ。CIの待ち時間は開発者の集中力を削ぎ、フロー状態を破壊する。本稿では、GitHub Actionsにおけるキャッシュ戦略を、単なる公式ドキュメントの引用ではなく、パッケージマネージャーの内部挙動レベルで最適化する「プロの作法」を伝授する。

—

1. なぜ「キャッシュのミス」が起きるのか?

GitHub Actionsの `actions/setup-node` は非常に強力だが、デフォルト設定に甘んじていると、キャッシュの更新タイミングが不適切になり、かえってオーバーヘッドを招く。

キャッシュの最適化において重要なのは「完全な依存関係のハッシュ化」と「各マネージャー固有のキャッシュディレクトリの分離」だ。

pnpmにおける「Content-Addressable Store」の真実

特に `pnpm` を使う場合、キャッシュ戦略は劇的に変わる。`pnpm` はプロジェクト間での共有キャッシュ(ストア)を前提に設計されている。GitHub Actionsでこれを活かすには、ストアの場所を固定し、保存対象として定義する必要がある。

—

2. 【ベストプラクティス】最適化されたYAML構成

以下は、`pnpm` を使用したプロジェクトで、キャッシュのヒット率を最大化し、ビルド時間を最小化するためのGitHub Actionsの設定例だ。

jobs:
build:
runs-on: ubuntu-latest
steps:

  • uses: actions/checkout@v4
  • name: Setup Node.js

uses: actions/setup-node@v4
with:
node-version: ’20’
# 重要: setup-nodeのキャッシュ機構を使いつつ、pnpmのストアを別管理する
cache: ‘pnpm’

  • name: Get pnpm store directory

id: pnpm-cache
shell: bash
run: |
# pnpmのキャッシュディレクトリパスを動的に取得
echo “STORE_PATH=$(pnpm store path)” >> $GITHUB_OUTPUT

  • name: Setup pnpm cache

uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
# lockfileが変更されたときのみキャッシュを更新(ハッシュ値の追跡)
key: ${{ runner.os }}-pnpm-store-${{ hashFiles(‘/pnpm-lock.yaml’) }}
restore-keys: |
${{ runner.os }}-pnpm-store-

  • name: Install dependencies

# –frozen-lockfileは必須。CI環境で勝手にlockfileを書き換えさせない
run: pnpm install –frozen-lockfile

ここがアーキテクトのこだわり:

  • `restore-keys` の活用: 完璧なハッシュ一致がなくても、過去のキャッシュから近い状態を復元することで、差分インストール(`pnpm` の強み)を最大限に活かす。
  • `–frozen-lockfile`: CIにおいてlockfileが改ざんされるリスクを排除し、再現性を担保する。これはチーム開発における「再現性のカオス」を防ぐための生命線だ。

—

3. 現場で「差」がつくプロのテクニック

開発時のCLI効率化:`pnpm` エイリアスとショートカット

ターミナルでのタイピングを減らすことは、脳の認知負荷を減らすことに直結する。`.zshrc` や `.bashrc` に以下の設定を入れてほしい。

pnpmのタイピングを極限まで減らす
alias pn=’pnpm’
alias pni=’pnpm install’
alias pnd=’pnpm dev’
alias pnb=’pnpm build’

依存関係を整理してキャッシュをクリーンに保つ(定期実行推奨)
alias pnp=’pnpm prune’

絶対に入れるべき神プラグイン:`lint-staged`

ビルドの前にローカルでこけるのは時間の無駄。`husky` と組み合わせて、コミット前に差分ファイルのみを対象にLint/Formatを走らせる設定を `package.json` に記述しておく。

“lint-staged”: {
“.{js,ts,jsx,tsx}”: [
“eslint –fix”,
“prettier –write”
]
}

これにより、CIで無駄なLinterエラーを検知して戻り作業が発生する確率がゼロになる。

—

4. チーム開発における「設定の共有化ルール」

テックリードとして、以下のルールをリポジトリの `CONTRIBUTING.md` に記載することを強く推奨する。

1. Lockfileのコミットは神聖な儀式である: 依存関係の変更は必ず1つのPRにまとめ、lockfileの差分を目視確認すること。
2. キャッシュの汚染を防ぐ: 開発環境の `node_modules` をクリーンに保つため、`pnpm store prune` を月1回実行する習慣を持つ。
3. CIのボトルネックを可視化する: GitHub Actionsのログで `Setup Node.js` や `Install dependencies` の時間が3分を超えたら即座に改善タスクを起票する。

最後に:エンジニアの時間は「投資」である

CI/CDのキャッシュ最適化は、単なる「速くするためのハック」ではない。ビルドを待っている間に失われる「エンジニアの思考の連鎖」を守るための防衛戦略だ。

ツールに振り回されるのではなく、その挙動を深く理解し、意図を持って制御する。その積み重ねこそが、最高峰のプロダクトを生み出すエンジニアの条件である。さあ、今すぐあなたのリポジトリのYAMLを開き、キャッシュの挙動をハックしてほしい。結果は、次のプッシュで必ず出るはずだ。

タイトルとURLをコピーしました