Aller au contenu
CI/CD & Automatisation medium

Cache Node.js/npm dans GitHub Actions

9 min de lecture

Les projets Node.js ont souvent des centaines de dépendances. Sans cache, chaque npm ci télécharge tout depuis le registre npm — 30 à 60 secondes minimum. Avec le cache, c'est réduit à quelques secondes.

  • Activer le cache intégré de setup-node pour npm, pnpm et yarn
  • Configurer actions/cache quand vous avez besoin de contrôle
  • Cacher les builds Next.js, Turborepo et ESLint
  • Gérer un monorepo à workspaces
  • Éviter les pièges du cache de node_modules et de pnpm

La méthode la plus simple :

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'npm' # Détecte automatiquement package-lock.json
- run: npm ci

setup-node détecte le gestionnaire de paquets selon les fichiers présents :

Fichier présentGestionnaire détecté
package-lock.jsonnpm
pnpm-lock.yamlpnpm
yarn.lockyarn
# Pour pnpm
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install
# Pour yarn
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'yarn'
- run: yarn install --frozen-lockfile

Pour plus de contrôle ou des cas spécifiques :

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
- run: npm ci
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'yarn'
- run: yarn install --immutable

Pour des gains maximum, cachez directement node_modules :

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
- name: Cache node_modules
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
id: cache-node-modules
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci

Au-delà des dépendances, les outils de build maintiennent leur propre cache sur disque. Le préserver d'un run à l'autre accélère franchement la compilation.

- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
.next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-
nextjs-${{ runner.os }}-
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-
turbo-${{ runner.os }}-
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- run: npm run lint -- --cache
name: Node.js CI
on:
push:
branches: [main]
pull_request:
branches: [main]
# Aucun droit par défaut : le job demande le minimum
permissions: {}
jobs:
build:
runs-on: ubuntu-24.04
permissions:
contents: read
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: '**/package-lock.json' # Tous les lockfiles
- run: npm ci --workspaces
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm -r build # Build tous les packages

Certains cas particuliers — modules natifs, tests end-to-end — ont leurs propres caches à connaître.

Pour les packages avec compilation native (sharp, bcrypt, etc.) :

- name: Cache native modules
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.npm
~/.node-gyp
key: native-${{ runner.os }}-node${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Cache Cypress
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

Deux problèmes reviennent souvent avec le cache Node.js. Voici comment les reconnaître et les corriger.

Fenêtre de terminal
npm ERR! `npm ci` can only install packages when your package.json and package-lock.json are in sync

La clé de cache doit inclure le hash du lockfile :

# ✅ Correct
key: npm-${{ hashFiles('**/package-lock.json') }}
# Assurez-vous d'installer pnpm avant setup-node
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
cache: 'pnpm'
  • Le cache intégré de setup-node (cache: 'npm' / 'pnpm' / 'yarn') couvre la majorité des projets.
  • Pour pnpm, installez pnpm/action-setup avant setup-node, sinon le store reste introuvable.
  • Cacher node_modules directement est plus rapide mais fragile : les scripts postinstall sont sautés sur un cache hit.
  • Cachez les builds (.next/cache, .turbo, .eslintcache) en plus des dépendances pour le gain maximal.
  • En monorepo, cache-dependency-path: '**/package-lock.json' couvre tous les workspaces.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn