CI/CD Next.js : guide pratique avec GitHub Actions pour tests et déploiement

Les logs défilent dans le terminal, les doigts tapent mécaniquement git pull && npm install && npm run build && pm2 restart. Troisième déploiement aujourd’hui — un petit correctif le matin, une optimisation d’API à midi, quelques retouches CSS maintenant. Trois serveurs, la même routine sur chacun.
Après le dernier, il est déjà 19 h.
Sur le chemin du retour, je me suis dit qu’il devait y avoir mieux. Je ne voulais plus répéter ces commandes sur le serveur à chaque modification, ni craindre qu’un serveur oublié laisse la prod dans un état incohérent. Les tests ? Souvent sautés — on voulait déployer vite, puis corriger en urgence.
Puis CI/CD et GitHub Actions ont changé le flux. Un push sur GitHub, et tests, build et déploiement tournent seuls. Le temps d’un café, la nouvelle version est en ligne.
Si le déploiement manuel vous épuise aussi, voici comment j’ai monté l’automatisation Next.js avec GitHub Actions — de la config de base au cas complet, pièges inclus.
Pourquoi adopter le CI/CD
Les douleurs du déploiement manuel
Au début de mes projets Next.js, le rituel était : coder en local, tester un peu, SSH sur le serveur, git pull, npm install, npm run build, pm2 restart. Une quinzaine de minutes quand tout allait bien.
Souvent, non.
Une fois, sur trois serveurs, les deux premiers étaient à jour, le troisième non — SSH coupé sans que je le voie. Le lendemain, des utilisateurs voyaient tantôt la nouvelle version, tantôt l’ancienne : le load balancer envoyait encore du trafic sur la machine non mise à jour. Une autre fois, j’ai déployé sans tests ; un bug critique en prod, rollback en urgence.
Pire encore : build ID différent par serveur. Chaque npm run build génère un nouvel ID ; derrière un load balancer, Next.js détecte le changement et force un hard refresh. L’expérience utilisateur en souffre.
Ce que le CI/CD change
En bref, il automatise ce qui est répétitif et fragile.
CI (intégration continue) : à chaque push, tests automatiques — unitaires, types, lint. Échec ? Pas de déploiement, il faut corriger.
CD (déploiement continu) : tests OK → build puis déploiement, sans intervention manuelle.
Aujourd’hui : code local → push GitHub → tests → build → déploiement. Environ cinq minutes, sans surveiller le terminal.
Chaque run laisse des logs : commit, résultats des tests, cible du déploiement. Le diagnostic est bien plus simple qu’avant.
Bases de GitHub Actions
Comment ça s’organise
Workflow, job, step — au début c’est dense, mais la logique est simple :
Workflow : le pipeline complet (ex. test + build + deploy), défini en YAML dans .github/workflows.
Job : tâche indépendante (ex. job « test », job « deploy »), en parallèle ou en chaîne.
Step : action concrète dans un job (checkout, install, test…).
Déclencheurs flexibles : push sur main, Pull Request, cron, etc.
Premier workflow
Créez .github/workflows/ci-cd.yml :
name: CI/CD Pipeline
# Déclenché à chaque push sur main
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
À chaque push sur main : checkout, Node 18, npm ci, build. Après le commit, l’onglet Actions du dépôt affiche l’exécution — la première coche verte fait plaisir.
Secrets pour les données sensibles
IP serveur, clés SSH, tokens API : jamais en clair dans le YAML. Utilisez Settings → Secrets and variables → Actions → New repository secret (ex. SERVER_HOST), puis ${{ secrets.SERVER_HOST }} dans le workflow. Les logs masquent les valeurs.
Pipeline de tests automatiques
Configuration
Tout ce qui peut l’être doit l’être : ESLint, TypeScript, Jest — la majorité des régressions est interceptée avant la prod.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm' # accélère les runs suivants
- name: Install dependencies
run: npm ci
- name: Lint check
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test -- --coverage
cache: 'npm' évite de retélécharger toutes les dépendances à chaque run.
Accélérer les tests
Au début, ~10 minutes par run. Après optimisation, ~3 minutes.
Cache Next.js :
- name: Cache Next.js build
uses: actions/cache@v3
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
Jobs parallèles si les vérifications sont indépendantes :
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test:
runs-on: ubuntu-latest
steps: [...]
type-check:
runs-on: ubuntu-latest
steps: [...]
La durée totale = le job le plus lent, pas la somme.
Pièges rencontrés
Tests OK en local, KO sur Actions : sur une PR, checkout fusionne par défaut la branche cible — un commit temporaire absent en local peut tout casser. Solution :
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
E2E lents : augmentez le timeout du job :
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15 # défaut : 6 min
Déploiement automatique
Vercel : le plus simple
Projet sur Vercel + repo GitHub connecté = déploiement à chaque push, presque sans config.
Pour n deployer qu’après les tests :
jobs:
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Token Vercel + org/project ID dans les Secrets. Bonus : preview par PR avec URL de prévisualisation.
Serveur auto-hébergé
Plus de contrôle, un peu plus de travail : SSH depuis Actions.
ssh-keygen -t ed25519 -C "github-actions"
Clé publique dans ~/.ssh/authorized_keys, privée dans GitHub Secrets (SSH_PRIVATE_KEY).
jobs:
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/my-nextjs-app
git pull origin main
npm install
npm run build
pm2 restart nextjs-app
Build ID identique sur plusieurs serveurs
Un seul build, puis distribution des artefacts :
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm ci
- run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: next-build
path: |
.next
public
deploy:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
server: [server1, server2, server3]
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: next-build
- name: Deploy to ${{ matrix.server }}
uses: appleboy/scp-action@master
with:
host: ${{ secrets[format('{0}_HOST', matrix.server)] }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: ".next,public"
target: "/var/www/my-nextjs-app"
Optimisation et bonnes pratiques
Quand le build échoue
Sans alerte, un push « réussi » peut masquer un déploiement raté. Slack par exemple :
- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Échec du déploiement — à vérifier'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
channel: '#deploy-notifications'
Branches : staging et production
develop → environnement de test, main → production :
on:
push:
branches:
- main
- develop
jobs:
deploy-staging:
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- [...]
deploy-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- [...]
Approbation manuelle pour la prod :
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
steps: [...]
Rollback
Tag par déploiement, workflow manuel workflow_dispatch :
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag de version pour le rollback'
required: true
jobs:
rollback:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag }}
- name: Deploy
[...]
Variables d’environnement
Secrets GitHub + injection au build :
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Exemple complet
Configuration test + build + déploiement staging/prod (extrait représentatif — voir le dépôt pour la version complète) :
name: Next.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run type-check
- run: npm run test -- --coverage
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- uses: actions/cache@v3
with:
path: |
~/.npm
.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }}
- uses: actions/upload-artifact@v3
with:
name: next-build
path: |
.next
public
package.json
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
# ...
deploy-production:
needs: build
if: github.ref == 'refs/heads/main'
environment:
name: production
# ...
Les PR ne font que les tests ; develop et main déclenchent build + déploiement selon la branche. Secrets typiques : API_URL, STAGING_HOST, PROD_HOST, SERVER_USER, SSH_PRIVATE_KEY, SLACK_WEBHOOK.
Conclusion
Passer du manuel à l’automatisé change vraiment le quotidien : plus de SSH répétitif, moins de tests oubliés, moins d’attente devant le terminal.
La mise en place GitHub Actions demande un peu de temps au départ, mais le retour est durable. Commencez par tests + build, ajoutez déploiement, notifications et rollback ensuite.
Un push, un café, la prod à jour — difficile de revenir en arrière. Créez votre premier fichier sous .github/workflows et regardez Actions tourner : vous comprendrez tout de suite.
Configuration complète CI/CD Next.js
De la création du workflow GitHub Actions aux étapes test, build et déploiement
⏱️ Estimated time: 2 hr
- 1
Step 1: Créer le workflow GitHub Actions
Créer .github/workflows/deploy.yml :
```yaml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run build
- run: npm test
```
Points clés :
• Déclencheur : push sur main
• Environnement : ubuntu-latest
• Étapes : checkout → setup-node → install → build → test - 2
Step 2: Configurer les tests
Ajouter les tests :
```yaml
- name: Run tests
run: npm test
- name: Type check
run: npm run type-check
- name: Lint
run: npm run lint
```
Points clés :
• Échec des tests = pas de déploiement
• Types et lint pour la qualité
• Plus de tests oubliés avant la prod - 3
Step 3: Configurer le build
Build :
```yaml
- name: Build
run: npm run build
env:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
```
Cache :
```yaml
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
```
Points clés : variables d'environnement, cache, vérifier les artefacts - 4
Step 4: Configurer le déploiement
Déploiement SSH :
```yaml
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /path/to/app
git pull
npm install
npm run build
pm2 restart app
```
Multi-serveurs : build unique dans Actions, puis distribution des artefacts — build ID cohérent, pas de hard refresh côté utilisateur.
FAQ
Pourquoi adopter le CI/CD ?
Comment configurer GitHub Actions ?
Comment éviter des build ID différents sur plusieurs serveurs ?
Comment configurer les étapes de test ?
Comment notifier l'équipe après un déploiement ?
Quelles bonnes pratiques CI/CD ?
7 min de lecture · Publié le: 20 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Guide complet Next.js sur Vercel : variables d'environnement, domaines et monitoring
Guide complet pour déployer Next.js sur Vercel : variables d'environnement, domaine personnalisé, certificat SSL et monitoring — en évitant les pièges courants des débutants.
Partie 40 sur 51
Suivant
Quitter Vercel : guide complet de l'auto-hébergement Next.js avec Docker
Marre de la facture Vercel ? Ce guide vous montre comment auto-héberger Next.js avec Docker : config standalone, proxy inverse et correctifs pour le rendu en streaming — économisez 300 à 500 $ par mois.
Partie 42 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire