GitHub Actions で Node.js プロジェクトの CI を構築する

Verified: checkout is at v7 (v7.0.1), setup-node at v7, cache at v5; Node 24 is Active LTS, 22 is Maintenance LTS, 20 is EOL. Rewriting accordingly.

概要

Node.js プロジェクトに GitHub Actions で CI を導入し、「Pull Request を出したら自動で Lint・型チェック・テストが走り、緑にならないとマージできない」状態をゼロから構築するハンズオンです。最小構成から始め、ジョブ分割による並列化、複数バージョンでのマトリクス実行、キャッシュによる高速化、そして必須チェック化まで段階的に進めます。

内容はパブリックに公開されている GitHub Actions・npm の仕様のみに基づいています。

環境

  • Node.js(LTS 系。本記事では Active LTS の 24 と Maintenance LTS の 22 を対象)
  • npm(package-lock.json をコミット済みであること)
  • GitHub リポジトリ(Actions が有効)

package.json に以下のスクリプトがある前提で進めます。

{
  "scripts": {
    "lint": "eslint .",
    "typecheck": "tsc --noEmit",
    "test": "vitest run"
  }
}

なぜ CI を入れるのか

CI を入れる前は、品質チェックが「人の記憶」と「手作業」に依存します。手作業は必ず抜けます。結果として、別の Node バージョンや他メンバーの環境で壊れる、テスト実行を忘れてマージされ main が壊れる、「動作確認しました」の基準が人によってバラバラでレビューの担保にならない、といった状態が生まれます。

GitHub Actions は push / pull_request などのイベントを起点に、決まった環境で決まったチェックを必ず走らせます。これで抜けを構造的に潰します。

ステップ1: 最小構成

.github/workflows/ 配下に YAML を置くだけで CI が動きます。

# .github/workflows/ci.yml
name: CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: "24"
      - run: npm ci
      - run: npm test

ポイントは3つです。on.pull_request で PR ごとに実行し push: branches: [main]main 反映時にも実行すること、依存インストールは npm install ではなく npm ci を使うこと、アクションはメジャーバージョン(@v7)を固定することです。

ステップ2: ジョブを分けて並列化する

Lint・型チェック・テストをジョブに分けると並列実行され、GitHub の UI 上で「どの工程で落ちたか」が一目で分かります。

name: CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: "24"
          cache: "npm"
      - run: npm ci
      - run: npm run lint

  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: "24"
          cache: "npm"
      - run: npm ci
      - run: npm run typecheck

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: "24"
          cache: "npm"
      - run: npm ci
      - run: npm test

全体の所要時間は「一番遅いジョブ」に収束するため、直列実行より速く終わります。

ステップ3: 複数 Node バージョンでマトリクス実行

サポートしたいバージョンを横断でテストします。ここでは Active LTS の 24 と Maintenance LTS の 22 を対象にします。

  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node-version: ["24", "22"]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - run: npm ci
      - run: npm test

fail-fast: false にすると、片方のバージョンが落ちても、もう片方の結果まで最後まで確認できます。

ステップ4: キャッシュで高速化する

actions/setup-nodecache: "npm" を指定すると、~/.npmpackage-lock.json のハッシュをキーに保存・復元され、依存が変わらない限り npm ci が速くなります。細かく制御したい場合は actions/cache を直接使います。

      - uses: actions/cache@v5
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

基本は setup-nodecache オプションで十分で、モノレポなど特殊なケースで actions/cache の直接利用を検討する、という優先順位で問題ありません。

なぜ npm ci なのか

CI では npm install ではなく npm ci を使います。package-lock.json を厳密に尊重し、ロックと package.json が食い違うとエラーで止まる(暗黙のバージョン更新が起きない)こと、node_modules を毎回クリーンにするため再現性が高いこと、クリーンインストール用に最適化され高速であることが理由です。「ローカルでは動くのに CI で壊れる」の一因はロックファイルの無視なので、CI では npm ci を徹底します。

確認方法

  1. ci.yml.github/workflows/ に置いてコミット・push する
  2. PR を作成する
  3. PR 画面下部の Checks 欄に各ジョブ(lint / typecheck / test)が並び、緑(成功)/赤(失敗)で表示される
  4. わざと test を落とすコミットを混ぜ、赤くなり、マージ前に検知できることを確認する
  5. Settings → Branchesmain を保護し、これらのジョブを Require status checks to pass に指定すると、チェックが緑でない PR はマージできなくなる

必須チェック化まで行って初めて、「壊れたものがマージされない」状態になります。ここが CI 導入の実質的なゴールです。

注意点

  • アクションのバージョン固定: @v7 のようにメジャーは固定する。厳密に信頼したい場合はコミットSHA固定も選択肢です。
  • フォークからの PR: フォーク元の PR では secrets が既定で渡りません。外部コントリビュータを受ける公開リポジトリでは設計に注意が必要です。
  • シークレットのログ露出: secrets はログにマスクされますが、echo などで自前展開すると漏れます。ログに出さない運用を徹底します。
  • 無料枠: パブリックリポジトリの実行は無料、プライベートは実行時間に無料上限があります。マトリクスの増やしすぎに注意します。

まとめ

  • .github/workflows/ci.yml を置くだけで、PR 起点の自動チェックが構築できる
  • ジョブ分割で並列化と失敗箇所の可視化を同時に得る
  • npm cicache: "npm" で再現性と速度を両立する
  • マトリクスで複数 Node バージョンを横断チェックする
  • 最後は Branch protection の必須チェック化まで行い、「壊れたものはマージされない」状態を作る

最小構成から始めて、チームの必要に応じて段階的に育てていくのが CI 構築の王道です。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください