Quand votre frontend a besoin d'un serveur : construire un pipeline CI/CD pour les applications SSR
Vous venez de terminer une fonctionnalité sur votre application Next.js. La build passe en local. Vous poussez en production. Mais au lieu d'une page fonctionnelle, les utilisateurs voient un écran blanc avec un loader qui tourne. Le processus serveur a bien démarré, mais il n'était pas réellement prêt à traiter les requêtes.
C'est le moment où vous réalisez que déployer un frontend rendu par le serveur est fondamentalement différent du déploiement d'un site statique. Le pipeline qui fonctionnait pour votre SPA React ou votre site Gatsby statique ne suffira plus.
La différence fondamentale : vous déployez un serveur, pas des fichiers
Avec un frontend statique, votre build produit des fichiers HTML, CSS et JavaScript. Vous les uploadez sur un CDN ou un bucket de stockage, et c'est terminé. Le déploiement est essentiellement une opération de copie de fichiers.
Avec le Server-Side Rendering (SSR), le résultat de la build inclut du code côté serveur qui doit s'exécuter en tant que processus. Des frameworks comme Next.js, Nuxt ou Remix génèrent un dossier contenant :
- Du code JavaScript qui s'exécute sur le serveur
- Des bundles JavaScript pour le client
- Des assets statiques comme les images et les polices
- Un point d'entrée (souvent
server.js) qui démarre l'application
Votre pipeline doit désormais traiter cette sortie comme une application qui s'exécute en continu, et non comme un ensemble de fichiers à servir. Cela change tout dans la façon de builder, tester et déployer.
Étape 1 : Builder avec la bonne cible
L'étape de build ressemble à première vue à celle d'un frontend statique. Vous exécutez npm run build ou la commande équivalente du framework. Mais le résultat est différent, tout comme ce que vous en faites.
Pour le SSR, le résultat de la build doit être empaqueté dans quelque chose qui peut s'exécuter sur un serveur. Si vous utilisez des conteneurs, cela signifie créer une image Docker qui inclut :
- Le code serveur compilé
- Les dépendances d'exécution (version de Node.js, bibliothèques système)
- Les fichiers de configuration nécessaires à l'exécution
- Le script de point d'entrée
Votre Dockerfile pourrait ressembler à ceci :
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY .next ./.next
COPY public ./public
EXPOSE 3000
CMD ["node", ".next/standalone/server.js"]
Le détail important : vous ne copiez pas l'intégralité du code source. Vous ne copiez que ce qui est nécessaire pour exécuter l'application. Cela permet de garder l'image légère et de réduire la surface d'attaque.
Étape 2 : Les health checks ne sont pas optionnels
C'est là que de nombreux pipelines SSR échouent. Le conteneur démarre, le processus s'exécute, et tout le monde suppose que l'application fonctionne. Mais "le processus tourne" n'est pas la même chose que "l'application peut servir des requêtes".
Votre application peut démarrer avec succès mais échouer à afficher des pages parce que :
- Une connexion à la base de données expire
- Une API externe est inaccessible
- Des variables d'environnement sont manquantes
- Un service requis n'est pas encore prêt
Ajoutez un endpoint de health check à votre application. Généralement, il se trouve à /health ou /api/health et renvoie un statut 200 lorsque l'application peut réellement traiter des requêtes. Votre pipeline doit appeler cet endpoint après le déploiement, avant de router le trafic vers la nouvelle version.
Si le health check échoue, arrêtez le pipeline. Ne laissez pas les utilisateurs voir des pages d'erreur ou des états de chargement infinis. L'équipe enquête, corrige le problème et relance le pipeline.
Étape 3 : Choisissez votre stratégie de déploiement
Vous avez deux voies courantes pour déployer des applications SSR : directement sur un serveur ou dans des conteneurs. Chacune a des implications différentes pour votre pipeline.
Le diagramme suivant illustre le pipeline SSR complet, de la build au déploiement, avec le point de décision critique du health check.
Déploiement direct sur serveur
Vous copiez le résultat de la build sur un serveur, arrêtez l'ancien processus et démarrez le nouveau. La préoccupation critique ici est la gestion de la transition.
Si vous tuez l'ancien processus immédiatement, toutes les requêtes en cours de traitement échoueront. Les utilisateurs verront des erreurs en pleine action. La solution est l'arrêt gracieux : l'ancien serveur cesse d'accepter de nouvelles requêtes mais termine le traitement de celles déjà en cours. Une fois terminées, le processus se ferme proprement. Ensuite, le nouveau serveur démarre et commence à accepter le trafic.
Votre script de pipeline doit coordonner cette passation. C'est faisable mais nécessite des scripts et une supervision minutieux.
Déploiement par conteneurs
Les conteneurs vous offrent plus de contrôle. Le pipeline construit une nouvelle image Docker, la pousse dans un registre et la déploie sur votre plateforme d'orchestration de conteneurs.
Voici un Dockerfile minimal qui empaquette l'application SSR compilée pour un déploiement conteneurisé :
FROM node:20-alpine
WORKDIR /app
# Copie des dépendances de production
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Copie des assets serveur et client compilés
COPY .next ./.next
COPY public ./public
# Exposition du port d'écoute
EXPOSE 3000
# Démarrage du serveur
CMD ["node", ".next/standalone/server.js"]
Avec Kubernetes, cela devient une mise à jour progressive :
- Un nouveau pod démarre avec la nouvelle image
- Le pod exécute son health check
- Une fois sain, le trafic est progressivement basculé vers le nouveau pod
- Les anciens pods sont terminés après avoir fini leurs requêtes en cours
Kubernetes gère automatiquement l'arrêt gracieux et le basculement de trafic. Votre pipeline doit simplement mettre à jour le manifeste de déploiement avec le nouveau tag d'image et l'appliquer.
Étape 4 : Suivez ce qui est en cours d'exécution
Après le déploiement, votre pipeline doit enregistrer quelle version est en cours d'exécution. Stockez le hash du commit, le tag de l'image ou l'horodatage du déploiement dans un endroit accessible. Cette information est précieuse lorsque vous devez :
- Revenir à une version précédente
- Enquêter sur le déploiement qui a introduit un bug
- Corréler des problèmes de performance avec des versions spécifiques
Une approche simple : taguez vos images Docker avec le hash du commit et stockez la correspondance dans une base de données ou un simple fichier texte. Vos outils de supervision peuvent ensuite référencer ces données lors des alertes.
Checklist pratique pour votre pipeline SSR
Avant de considérer votre pipeline comme prêt pour la production, vérifiez ces points :
- Le résultat de la build est empaqueté dans un artefact déployable (image Docker ou package serveur)
- Un endpoint de health check existe et renvoie un statut significatif
- Le pipeline attend que le health check réussisse avant de router le trafic
- Le pipeline s'arrête et alerte si le health check échoue
- L'arrêt gracieux est configuré (l'ancien processus termine les requêtes en cours)
- La stratégie de mise à jour progressive est testée (pas de temps d'arrêt pendant le déploiement)
- Les informations de version sont stockées et accessibles après le déploiement
- La procédure de rollback est documentée et testée
Ce qu'il faut retenir
Un frontend SSR n'est pas un site statique. C'est une application serveur qui, par hasard, génère du HTML. Traitez votre pipeline en conséquence : construisez pour l'exécution, vérifiez avec des health checks, déployez avec des stratégies sans temps mort, et sachez toujours quelle version sert vos utilisateurs. Lorsque vous maîtrisez ces fondamentaux, vos utilisateurs ne voient jamais l'écran blanc. Ils voient juste une page rapide et fonctionnelle.