frontend / nextjs
Déploiement et variables d'environnement
Explication
Ce que vous allez apprendre
- Comprendre la différence critique entre une variable normale et une variable
NEXT_PUBLIC_ - Valider
process.envavec un schéma Zod dès le démarrage de l'application - Réduire la taille d'une image Docker avec
output: "standalone" - Éviter d'ignorer silencieusement les erreurs TypeScript/ESLint en CI
- Configurer un pipeline de build minimal avec les bonnes variables secrètes
Dans quel contexte ?
Un développeur ajoute une clé d'API tierce dans son fichier .env.local pour tester une intégration de paiement en local. Pressé, il la nomme NEXT_PUBLIC_STRIPE_SECRET_KEY en copiant le préfixe d'une autre variable du projet, sans réaliser que ce préfixe précis rend la variable visible dans le code source envoyé à TOUS les navigateurs qui visitent le site. Cette leçon explique cette règle avant qu'elle ne cause un incident de sécurité réel, ainsi que les autres pièges classiques d'un déploiement en production.
D'abord, une réalité à accepter
Sur sa machine, on peut se permettre des raccourcis. Ignorer une erreur TypeScript, coder en dur une URL d'API, tester avec une base de données locale sans conséquence.
En production, ces mêmes raccourcis deviennent des risques réels. Une clé secrète oubliée dans le code source, un build qui échoue silencieusement, une variable manquante qui plante l'application devant un vrai utilisateur.
Il existe un piège particulièrement dangereux et spécifique à Next.js : le préfixe NEXT_PUBLIC_. C'est LA distinction à absolument maîtriser avant de gérer des variables d'environnement.
Voyons d'abord le cas normal. Une variable comme DATABASE_URL reste strictement côté serveur, jamais visible du client.
Mais toute variable préfixée NEXT_PUBLIC_ change complètement de comportement. Elle est délibérément INCLUSE dans le bundle JavaScript envoyé au navigateur, où n'importe qui peut l'inspecter dans les outils développeur.
La conséquence est grave si on se trompe. Mettre un secret (clé API privée, mot de passe de base de données) derrière ce préfixe par erreur revient à le publier publiquement sur Internet.
| Type de variable | Visible dans le bundle client | Exemple |
|---|---|---|
| Normale (sans préfixe) | Non, reste côté serveur | DATABASE_URL, SESSION_SECRET |
NEXT_PUBLIC_* | Oui, incluse dans le JS envoyé au navigateur | NEXT_PUBLIC_API_URL, NEXT_PUBLIC_GA_ID |
Piège dangereux
Nommer une variable secrète NEXT_PUBLIC_STRIPE_SECRET_KEY par erreur de copier-coller la rend visible dans le code source de la page, consultable par n'importe qui via les outils de développement du navigateur. Une clé secrète ne doit JAMAIS porter le préfixe NEXT_PUBLIC_.
Une fois cette distinction bien comprise, il reste un autre risque à anticiper : la variable manquante découverte trop tard. Un scénario classique à éviter : une variable mal formée qui n'est détectée qu'au moment où le code l'utilise, souvent en production sous charge réelle.
La solution : valider process.env avec un schéma dès le démarrage. Un outil comme Zod fait échouer le build ou le déploiement IMMÉDIATEMENT si une variable est absente, plutôt que de laisser un bug latent atteindre les utilisateurs.
Passons maintenant à un sujet différent : la taille de l'image de déploiement. Par défaut, une image Docker contenant une application Next.js embarque tout node_modules, souvent volumineux.
L'option output: "standalone" règle ce problème. Elle génère un dossier minimal ne contenant QUE les fichiers réellement nécessaires à l'exécution, réduisant considérablement la taille de l'image.
Le piège fréquent pour finir : configurer ignoreBuildErrors ou ignoreDuringBuilds "temporairement" pour débloquer un déploiement urgent, et ne jamais y revenir. Ces erreurs ignorées en CI finissent presque toujours par devenir des bugs en production, découverts bien plus tard à un coût bien plus élevé.
Commandes & code
Déploiement et variables d'environnement
# .env.local — jamais commité (ajouter à .gitignore)
DATABASE_URL="postgresql://user:pass@host:5432/db"
SESSION_SECRET="une-clé-longue-et-aléatoire"
# Préfixe NEXT_PUBLIC_ : exposé au bundle CLIENT — attention aux secrets !
NEXT_PUBLIC_API_URL="https://api.example.com"
NEXT_PUBLIC_GA_ID="G-XXXXXXX"// lib/env.ts — valider les variables d'env au démarrage avec Zod
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
SESSION_SECRET: z.string().min(32),
NEXT_PUBLIC_API_URL: z.string().url(),
});
// throw immédiatement si une variable manque ou est mal formée
export const env = envSchema.parse(process.env);// next.config.js — configuration de production
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone", // build minimal pour Docker (ne copie que les deps utilisées)
poweredByHeader: false, // retire le header "X-Powered-By: Next.js"
compress: true,
eslint: { ignoreDuringBuilds: false },
typescript: { ignoreBuildErrors: false }, // ne JAMAIS ignorer les erreurs en CI
async redirects() {
return [{ source: "/old-blog/:slug", destination: "/blog/:slug", permanent: true }];
},
};
module.exports = nextConfig;# Dockerfile — build multi-stage optimisé grâce à output: "standalone"
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]# .github/workflows/deploy.yml — CI minimal avant déploiement
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SESSION_SECRET: ${{ secrets.SESSION_SECRET }}Résumé
- Seules les variables préfixées
NEXT_PUBLIC_sont envoyées au bundle client — jamais y mettre un secret. - Valider
process.envavec un schéma (Zod) fait échouer le build tôt plutôt qu'en production. output: "standalone"réduit drastiquement la taille de l'image Docker.- Ne jamais ignorer les erreurs TypeScript/ESLint au build en CI (
ignoreBuildErrors).
Exercices pratiques
Mission : la clé secrète Stripe visible dans les outils développeur
Objectif : Corriger une fuite de secret due à un mauvais préfixe de variable et sécuriser le pipeline de build contre les erreurs ignorées.
Contexte
Un développeur pressé a ajouté NEXT_PUBLIC_STRIPE_SECRET_KEY="sk_live_..." dans .env.local en copiant le préfixe d'une autre variable du projet. La clé secrète Stripe est désormais visible dans le code source envoyé à tous les navigateurs. Par ailleurs, next.config.js contient typescript: { ignoreBuildErrors: true }, ajouté "temporairement" il y a six mois pour débloquer un déploiement urgent, jamais retiré depuis. Enfin, lib/env.ts n'existe pas : une variable manquante n'est découverte qu'au moment où le code l'utilise, en production.