Un pipeline CI/CD Next.js avec GitHub Actions bien réglé, c''est la différence entre un déploiement qu''on redoute et un déploiement qu''on oublie. Sur nos projets clients, chaque push sur main déclenche lint, vérification de types, tests et build sans qu''un humain touche quoi que ce soit. Résultat : les régressions sont attrapées avant la production, pas après.
Le problème, c''est qu''un workflow mal configuré fait exactement l''inverse. Un pipeline qui met quatre minutes à réinstaller node_modules et à rebuild Next.js from scratch à chaque commit, personne ne veut l''attendre — et une CI lente finit toujours contournée. La bonne nouvelle : deux couches de cache bien placées font tomber ce temps sous les deux minutes.
Cet article donne la structure exacte d''un pipeline CI/CD Next.js sur GitHub Actions : le squelette du workflow, le cache pnpm, le cache de build .next/cache, la barrière qualité et le déclenchement du déploiement. Du copier-coller directement réutilisable.
Pourquoi un pipeline CI/CD change tout sur un projet Next.js
Un projet Next.js sans CI repose sur une hypothèse fragile : que chaque développeur pense à lancer pnpm lint, tsc et les tests avant de pousser. En pratique, ça arrive une fois sur trois un vendredi soir. La CI supprime cette dépendance à la discipline humaine. Le pipeline exécute les mêmes commandes, dans le même environnement propre, à chaque push et chaque pull request.
L''intérêt dépasse la détection de bugs. Une CI qui bloque le merge tant que les vérifications échouent transforme la branche main en source de vérité toujours déployable. Plus de « ça marchait sur ma machine » : l''environnement GitHub Actions repart d''un runner vierge, sans cache local trompeur ni variable d''environnement oubliée. C''est aussi ce qui rend les stratégies de déploiement avancées fiables, un sujet qu''on détaille dans notre article sur les déploiements blue-green et canary.
Le coût d''entrée est faible : un seul fichier YAML dans .github/workflows. Le vrai travail est de le garder rapide, sinon il devient un péage qu''on cherche à éviter.
Anatomie d''un workflow GitHub Actions
Un workflow GitHub Actions se lit de haut en bas : un déclencheur (on), un ou plusieurs jobs, et pour chaque job une suite d''étapes. Pour du CI/CD Next.js, on déclenche sur les push vers main et sur les pull requests, ce qui couvre à la fois la validation avant merge et la validation de la branche de production.
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# setup-node lit le champ engines et fige la version Node du runner :
# une CI qui tourne sur une autre version que la prod ne prouve rien.
- uses: actions/setup-node@v4
with:
node-version: 22
Le point à ne pas rater : figer explicitement la version de Node. Un runner GitHub met à jour ses versions par défaut sans prévenir, et un build qui passe aujourd''hui sur Node 20 puis casse demain sur Node 24 est un cauchemar à diagnostiquer. On aligne la version de la CI sur celle de la production, point.
Mettre en cache pnpm pour des installs rapides
L''installation des dépendances est le premier goulot d''étranglement. Sans cache, chaque run télécharge l''intégralité des paquets depuis le registre — plusieurs centaines de mégaoctets pour un projet Next.js moyen. pnpm règle une partie du problème via son store partagé et ses hard links, qui réduisent les temps d''install de 40 à 60 % par rapport à npm, mais encore faut-il persister ce store entre les runs.
- uses: pnpm/action-setup@v4
with:
version: 9
# cache: pnpm indique à setup-node de persister le store pnpm
# entre les runs, keyé sur le hash du lockfile. Le cache n''est
# invalidé que quand les dépendances changent réellement.
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
# --frozen-lockfile interdit toute mise à jour silencieuse d''une
# dépendance transitive : la CI installe l''arbre exact du lockfile,
# ce qui ferme une porte d''entrée classique aux attaques supply-chain.
- run: pnpm install --frozen-lockfile
Le drapeau --frozen-lockfile n''est pas un détail de performance, c''est une garantie de reproductibilité et de sécurité. Sans lui, pnpm peut résoudre une version plus récente d''une dépendance transitive à chaque run, ce qui rend vos builds non déterministes et ouvre la porte à des paquets non audités. Avec un cache correctement keyé sur pnpm-lock.yaml, les taux de hit se situent entre 70 et 90 %.
Cacher le build Next.js avec .next/cache
Le second goulot, c''est la compilation. Next.js écrit un cache incrémental dans .next/cache : trace des modules, résultats de Webpack ou Turbopack, pages déjà compilées. Sur un runner neuf ce dossier est vide, donc chaque build repart de zéro. La documentation officielle recommande de le persister avec actions/cache.
- uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
# La clé combine lockfile ET fichiers source : elle change dès
# qu''un fichier bouge, mais restore-keys récupère le cache le plus
# proche pour rebuild en incrémental plutôt qu''entièrement.
key: nextjs-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.{ts,tsx,js,jsx}') }}
restore-keys: |
nextjs-${{ hashFiles('pnpm-lock.yaml') }}-
- run: pnpm build
La subtilité est dans la stratégie de clé. Une clé purement basée sur le lockfile ne s''invalide jamais quand le code change, et Next.js peut alors servir un cache périmé. Une clé qui inclut le hash des sources s''invalide trop souvent pour servir de hit direct. La combinaison des deux avec restore-keys donne le meilleur compromis : hit exact quand rien n''a bougé, restauration partielle sinon. Attention au quota : GitHub limite le cache à 10 Go partagés par dépôt, et les entrées non utilisées depuis sept jours sont évincées.
Lint, types et tests : la barrière qualité
Un cache rapide ne sert à rien s''il valide du code cassé plus vite. Le cœur du pipeline reste la barrière qualité : trois vérifications qui doivent toutes passer avant qu''un merge soit autorisé. L''ordre compte, car on veut échouer sur le contrôle le plus rapide en premier pour ne pas gaspiller de minutes de runner.
D''abord le lint, qui attrape les erreurs de style et les patterns dangereux en quelques secondes. Ensuite tsc --noEmit, qui vérifie l''intégralité des types sans produire de fichiers — c''est souvent l''étape qui rattrape les vraies régressions, celles qu''un test unitaire ne couvre pas encore. Enfin les tests eux-mêmes, unitaires puis end-to-end si vous en avez. Sur un monorepo, ces commandes se pilotent élégamment via Turborepo, un point qu''on développe dans notre guide sur le monorepo Next.js avec Turborepo et pnpm.
Configurez ensuite ces vérifications comme required status checks dans les règles de protection de branche. Sans cette étape, la CI reste un simple indicateur qu''on peut ignorer d''un clic sur « merge anyway ».
Du build au déploiement automatisé avec GitHub Actions
Une fois la barrière qualité franchie, le déploiement devient une simple étape conditionnelle. On ne déploie que depuis main, jamais depuis une pull request, ce qui évite qu''une branche en cours parte accidentellement en production. La condition if: github.ref == ''refs/heads/main'' sur le job de déploiement suffit à garantir cette règle.
Le pattern que nous privilégions sépare nettement les deux responsabilités : un job quality qui valide, puis un job deploy déclaré avec needs: quality, donc qui ne s''exécute que si toutes les vérifications sont vertes. Le job de déploiement construit l''image Docker, la pousse sur un registre, puis déclenche la mise à jour sur le serveur ou le cluster. Pour un rollout sans coupure, ce job orchestre une bascule blue-green plutôt qu''un remplacement brutal des conteneurs.
Les secrets — clés de registre, tokens SSH, variables d''environnement de production — vivent dans les repository secrets de GitHub, jamais dans le YAML. GitHub Actions les injecte au runtime et les masque dans les logs. C''est la seule façon acceptable de manipuler des identifiants de production dans un pipeline versionné publiquement ou partagé au sein d''une équipe.
En pratique
Un pipeline CI/CD Next.js avec GitHub Actions efficace tient en une centaine de lignes de YAML et repose sur trois piliers : figer l''environnement d''exécution, cacher agressivement les deux étapes lentes (install pnpm et build .next/cache), et bloquer le merge derrière une barrière qualité non négociable. Le reste — matrices, jobs parallèles, déploiement conditionnel — n''est que du raffinement au-dessus de ces fondations.
Le piège récurrent que nous voyons sur les projets repris, c''est la CI qu''on a écrite une fois puis abandonnée : versions de dépendances non figées, cache mal keyé qui ne hit jamais, vérifications présentes mais non requises au merge. Un pipeline qu''on ne maintient pas dérive vers l''inutile en quelques mois. Traitez votre workflow comme du vrai code : relisez-le, mesurez son temps d''exécution, et supprimez ce qui ne sert plus. Le sujet du cache mérite d''ailleurs le même soin côté application, comme on l''explore pour le caching dans Next.js App Router.
Chez Kreio, agence Next.js basée à Évreux (Normandie), on applique ces patterns sur des projets clients en production. Besoin d''un audit de votre pipeline ou d''un renfort tech ? Parlons-en.