Changer le thème

Quitter Vercel : guide complet de l'auto-hébergement Next.js avec Docker

Easton editorial illustration: performance inspection lens

Fin du mois dernier, j’ai ouvert la page de facturation Vercel comme d’habitude. 47,32 $.

Calcul rapide : le trafic du blog n’a augmenté que d’environ 20 % ce mois-ci, pourquoi la facture a-t-elle doublé ? En détaillant, le problème venait des appels aux serverless functions — une route API mal mise en cache s’exécutait trois fois à chaque rafraîchissement de page.

L’expérience de développement sur Vercel est vraiment fluide : git push et déploiement automatique, réseau edge mondial, fonctionnalités prêtes à l’emploi. Mais dès que le trafic monte un peu, la facture décolle. Le forfait Pro à 20 $ n’est qu’un point de départ ; ce qui coûte cher, ce sont les composants facturés à l’usage.

À ce moment-là, j’ai réalisé qu’il était temps de migrer ce projet ailleurs.

Cet article retrace ma migration complète d’un projet Next.js de Vercel vers l’auto-hébergement Docker. Pièges rencontrés, documentation consultée, configurations testées — tout est ici. Si vous envisagez l’auto-hébergement ou si vous avez déjà essayé et rencontrez des problèmes étranges (ressources statiques 404, rendu streaming inactif), j’espère que cela vous aidera.

$35-50
Coût mensuel Vercel
Variable selon le trafic
$12
Coût mensuel auto-hébergé
Coût fixe
$300-500
Économies annuelles
Plusieurs projets possibles
200MB
Taille image Docker
Après build multi-étapes
Source: Données terrain

Pourquoi quitter Vercel ?

Clarifions d’abord : je ne veux pas dénigrer Vercel. Pour beaucoup de cas, c’est toujours la meilleure option — surtout les projets entreprise, le réseau edge mondial, ou les équipes sans compétences ops. Pour les projets perso et petites équipes, le coût reste un vrai frein.

La logique tarifaire de Vercel

Le forfait gratuit semble généreux : 100 Go de bande passante, 1 million de Edge Requests. Le problème, c’est qu’un projet un peu trafiqué dépasse vite ces quotas. Une fois passé au Pro (20 $/mois), ce n’est que l’entrée :

  • Appels Serverless Function : facturation au-delà de 1 million
  • Temps d’exécution edge : facturation au-delà de 1 million GB-s
  • Optimisation d’images : facturation au-delà de 5 000 utilisations
  • Bande passante : facturation par Go au-delà de 1 To

Le plus piégeux : ces volumes sont difficiles à anticiper. Une route API mal cachée, une page crawlée en boucle par des bots, et la facture explose.

Combien économise-t-on en auto-hébergement ?

J’ai fait le calcul. Mon projet coûtait environ 35-50 $/mois sur Vercel, selon le trafic. Après migration vers un serveur DigitalOcean à 12 $/mois :

  • Serveur : 12 $/mois (2 cœurs, 4 Go RAM, suffisant pour 2-3 apps Next.js)
  • Cloudflare CDN : gratuit (déjà utilisé)
  • Stockage supplémentaire : 0 $ (disque local suffisant)

Économie mensuelle : 25-40 $, soit 300-500 $ par an. Surtout, ce coût est fixe — pas d’explosion en cas de pic de trafic.

Quand l’auto-hébergement est-il adapté ?

Tout le monde ne devrait pas auto-héberger. Je pense que ces conditions conviennent :

  • ✅ Bases Linux/Docker déjà acquises
  • ✅ Trafic relativement stable, pas besoin de réseau edge mondial
  • ✅ Acceptation d’un déploiement manuel de 5 à 10 minutes
  • ✅ Budget serré (projets perso, early stage)

À l’inverse, dans ces cas, restez sur Vercel :

  • ❌ Équipe sans compétences ops et sans envie d’apprendre
  • ❌ Trafic très variable, besoin d’auto-scaling
  • ❌ Besoin d’Analytics, Edge Config et autres fonctionnalités Vercel
  • ❌ Budget confortable, efficacité de développement prioritaire

Réfléchissez avant de vous lancer — ne vous épuisez pas pour économiser quelques dollars.

Configuration Docker essentielle pour Next.js

Entrons dans le vif du sujet. Le déploiement Docker de Next.js repose sur trois points clés. Maîtrisez-les et vous éviterez la plupart des problèmes.

1. Mode de sortie Standalone

C’est l’étape la plus critique. Par défaut, next build génère de nombreux fichiers, y compris node_modules complet. Dans Docker, le volume est énorme et le démarrage lent.

Ajoutez cette ligne dans next.config.js :

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
}

module.exports = nextConfig

Après npm run build, vous verrez le dossier .next/standalone contenant :

  • server.js : script de démarrage
  • node_modules allégé : uniquement les packages runtime
  • Code de l’application

Point clé : le mode standalone ne copie pas automatiquement public et .next/static. Il faut les copier manuellement dans le dossier standalone, sinon toutes les ressources statiques renvoient 404. Ce piège m’a coûté deux jours.

2. Dockerfile multi-étapes

Voici le Dockerfile que j’utilise, avec des commentaires détaillés :

# ============ Phase 1 : Installation des dépendances ============
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Copier uniquement les manifests pour le cache Docker
COPY package.json package-lock.json ./
RUN npm ci

# ============ Phase 2 : Build de l'application ============
FROM node:20-alpine AS builder
WORKDIR /app

# Copier dépendances et code source
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Variables d'environnement au build (si nécessaire)
ENV NEXT_TELEMETRY_DISABLED=1

# Build
RUN npm run build

# ============ Phase 3 : Exécution en production ============
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# Créer un utilisateur non root (bonne pratique sécurité)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copier le dossier public (ressources statiques)
COPY --from=builder /app/public ./public

# Copier la sortie standalone
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
# Copier les fichiers static (CSS/JS compilés)
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Commande de démarrage
CMD ["node", "server.js"]

Points importants :

  1. Build en trois phases : dépendances, build et exécution séparés ; l’image finale ne contient que le runtime — volume de 1,5 Go à 200 Mo
  2. COPY --from=builder /app/public : ne l’oubliez pas, sinon favicon, robots.txt inaccessibles
  3. COPY ./.next/static : encore plus critique — sans cela, tout JS/CSS renvoie 404
  4. Utilisateur non root : bonne pratique ; ne lancez pas l’app en root en production

3. Le piège des variables d’environnement

Je suis tombé dedans aussi. Les variables Next.js se divisent en deux :

  • Variables de build : préfixe NEXT_PUBLIC_, compilées dans le code
  • Variables de runtime : côté serveur, ex. adresse base de données

En mode Standalone, runtimeConfig ne fonctionne pas. L’approche recommandée avec App Router :

// app/api/example/route.ts
export async function GET() {
  // Lecture directe depuis process.env
  const dbUrl = process.env.DATABASE_URL
  // ...
}

Passer les variables au runtime Docker :

docker run -p 3000:3000 \
  -e DATABASE_URL="postgres://..." \
  -e API_KEY="xxx" \
  your-image-name

Ou avec docker-compose.yml :

version: '3.8'
services:
  nextjs:
    image: your-image-name
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: "postgres://..."
      API_KEY: "xxx"
    restart: unless-stopped

Attention : les variables NEXT_PUBLIC_ doivent être définies au build ; impossible de les modifier au runtime. Pour une config dynamique, utilisez uniquement des variables côté serveur.

Points clés du proxy inverse

Vous pouvez exposer le conteneur Next.js directement sur Internet, mais évitez. Une app Node.js nue face aux requêtes malveillantes et slowloris ne tient pas longtemps. Le proxy inverse n’est pas optionnel — c’est indispensable.

Pourquoi un proxy inverse ?

  1. Sécurité : filtrer les requêtes malveillantes, rate limiting, anti-DDoS
  2. HTTPS : gestion centralisée des certificats SSL
  3. Multi-applications : plusieurs projets sur un serveur, routage par domaine/chemin
  4. Cache des ressources statiques : alléger la charge sur l’app

J’utilise Nginx, stable et fiable. Pour une config plus simple, Caddy convient aussi (HTTPS automatique, syntaxe plus claire).

Exemple de configuration Nginx

server {
    listen 80;
    server_name yourdomain.com;
    
    # Redirection HTTPS (si SSL configuré)
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;
    
    # Certificats SSL (Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    
    # Proxy inverse vers le conteneur Next.js
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        
        # En-têtes requis
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Clé : désactiver le buffering pour le rendu streaming
        proxy_buffering off;
        proxy_cache off;
        proxy_set_header X-Accel-Buffering no;
        
        # Support WebSocket (si nécessaire)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    
    # Cache des ressources statiques (optionnel mais recommandé)
    location /_next/static/ {
        proxy_pass http://localhost:3000;
        proxy_cache_valid 200 60m;
        add_header Cache-Control "public, max-age=3600, immutable";
    }
}

Trois configurations clés :

  1. proxy_buffering off : sans cela, la sortie streaming se bloque
  2. X-Accel-Buffering: no : indique explicitement à Nginx de ne pas bufferiser le corps de réponse
  3. Support WebSocket : avec Socket.io ou fonctionnalités temps réel, l’en-tête Upgrade est obligatoire

Configuration simplifiée avec Caddy

Si Nginx vous semble trop verbeux, essayez Caddy :

yourdomain.com {
    reverse_proxy localhost:3000 {
        # Caddy ne bufferise pas par défaut, pas de config spéciale
    }
}

C’est tout. Caddy gère automatiquement Let’s Encrypt et le renouvellement.

Intégration Docker Compose

Conteneuriser Nginx aussi simplifie la gestion :

version: '3.8'
services:
  nextjs:
    build: .
    restart: unless-stopped
    environment:
      DATABASE_URL: "postgres://..."
    # Pas d'exposition sur l'hôte, accès nginx uniquement
    expose:
      - "3000"
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./certs:/etc/letsencrypt
    depends_on:
      - nextjs
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

Notez que le service nextjs utilise expose et non ports — seuls les conteneurs du même réseau y accèdent, plus sécurisé.

Résoudre l’échec du rendu streaming

Ce problème m’a occupé une journée entière. En local, le chat IA fonctionnait parfaitement ; déployé sur Docker, la sortie streaming échouait — soit tout arrivait d’un coup après une longue attente, soit ça bloquait.

Symptômes

Typiquement :

  • Réponses streaming OpenAI/Anthropic inactives
  • Server-Sent Events (SSE) sans push temps réel
  • La page attend longtemps puis se rafraîchit d’un coup, sans effet mot par mot

En local avec npm run dev, tout va bien ; en production, ça casse.

Cause racine

Deux sources possibles :

  1. Buffering du proxy inverse : Nginx bufferise par défaut le corps de réponse avant de l’envoyer au client
  2. Runtime Next.js : les routes API hors Edge Runtime ne supportent pas toujours le streaming

Solution 1 : Configuration Nginx

Les trois lignes mentionnées plus haut, à répéter :

proxy_buffering off;
proxy_cache off;
proxy_set_header X-Accel-Buffering no;

Ces trois lignes doivent être dans le bloc location /. Puis redémarrer Nginx :

nginx -t  # Tester la syntaxe
nginx -s reload  # Recharger la config

Solution 2 : Utiliser Edge Runtime

Si votre route API sert au streaming (ex. chat IA), ajoutez en tête du fichier :

// app/api/chat/route.ts
export const runtime = 'edge'

export async function POST(req: Request) {
  const stream = new ReadableStream({
    async start(controller) {
      // Votre logique streaming
      const response = await openai.chat.completions.create({
        model: 'gpt-4',
        messages: [...],
        stream: true,
      })

      for await (const chunk of response) {
        controller.enqueue(chunk.choices[0]?.delta?.content || '')
      }
      
      controller.close()
    },
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  })
}

Edge Runtime est un runtime léger optimisé pour le streaming, plus stable en Docker.

Vérifier la correction

Testez avec curl — une sortie ligne par ligne confirme le succès :

curl -N http://yourdomain.com/api/chat \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello"}'

L’option -N désactive le buffering ; le contenu doit apparaître progressivement.

Toujours bloqué ? Vérifiez ceci

  1. Proxy Cloudflare : le nuage orange bufferise aussi. Désactivez-le (nuage gris) ou passez Pro (streaming supporté)
  2. Health check Docker : certaines configs peuvent perturber le streaming — vérifiez healthcheck dans docker-compose.yml
  3. Load balancer : un LB devant peut aussi bufferiser — configuration séparée nécessaire

Diagnostic et correction des problèmes courants

Voici les pièges que j’ai rencontrés et les questions fréquentes de la communauté — environ 80 % des échecs de déploiement.

Problème 1 : Ressources statiques 404

Symptôme : la page s’affiche mais sans style, console pleine de 404 sur /_next/static/...

Cause : le Dockerfile ne copie pas correctement .next/static.

Correction : vérifiez ces deux lignes :

COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

Si 404 persiste, vérifiez les permissions :

COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

Problème 2 : Échec du build Docker

Symptôme : docker build échoue avec « Could not find a production build in the ‘.next’ directory »

Cause : .dockerignore mal configuré ou ordre de build incorrect.

Correction : créez .dockerignore pour exclure les dossiers inutiles :

.next
node_modules
.git
.env*.local
out
.DS_Store
*.log

Note : .next doit être exclu — on rebuild dans le conteneur.

Problème 3 : Variables d’environnement inactives

Symptôme : process.env.DATABASE_URL retourne undefined

Cause : mauvaise transmission ou confusion build vs runtime.

Correction :

  1. Variables runtime (BDD, clés API) : docker run -e ou docker-compose.yml :

    docker run -e DATABASE_URL="..." your-image
  2. Variables de build (NEXT_PUBLIC_*) : au docker build :

    docker build --build-arg NEXT_PUBLIC_API_URL="https://api.example.com" .

    Dans le Dockerfile :

    ARG NEXT_PUBLIC_API_URL
    ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

Problème 4 : Mémoire insuffisante au build

Symptôme : build qui bloque ou « JavaScript heap out of memory »

Cause : limite mémoire Node.js par défaut insuffisante pour un gros projet Next.js.

Correction : augmenter la mémoire en phase builder :

# Phase builder
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm run build

Ou limiter les ressources avec Docker BuildKit :

docker build --memory=8g --memory-swap=8g -t your-image .

Problème 5 : Conteneur démarré mais inaccessible

Symptôme : conteneur actif, mais http://localhost:3000 refuse la connexion.

Cause : Next.js écoute par défaut sur 127.0.0.1, inaccessible depuis l’extérieur du conteneur.

Correction : dans le Dockerfile :

ENV HOSTNAME="0.0.0.0"
ENV PORT=3000

Ou au lancement :

docker run -p 3000:3000 -e HOSTNAME="0.0.0.0" your-image

Commandes de diagnostic rapide

En cas de problème, lancez d’abord :

# 1. Vérifier que le conteneur tourne
docker ps

# 2. Consulter les logs
docker logs <container-id>

# 3. Entrer dans le conteneur et inspecter
docker exec -it <container-id> sh
ls -la .next/
ls -la public/

# 4. Tester le service interne
docker exec -it <container-id> wget -O- http://localhost:3000

# 5. Vérifier le mapping de ports
docker port <container-id>

Conclusion

Migrer de Vercel vers l’auto-hébergement Docker n’est pas aussi effrayant qu’on pourrait croire. La config initiale prend du temps, mais une fois en place, la maintenance est légère. Aujourd’hui : 12 $/mois fixe, trois projets Next.js, sans crainte d’une facture qui explose.

Récapitulatif des trois configurations clés :

  1. Mode Standalone — une ligne dans next.config.js, copier manuellement public et .next/static
  2. Dockerfile multi-étapes — trois phases, image finale ~200 Mo, démarrage rapide
  3. Proxy inverse — Nginx doit désactiver le buffering (proxy_buffering off), sinon le rendu streaming est mort

Bloqué sur le streaming ? Dans 99 % des cas, c’est le buffering du proxy. Ajoutez export const runtime = 'edge' et vous devriez être bon.

Vercel vs auto-hébergement

DimensionVercelDocker auto-hébergé
Vitesse de déploiement⚡️ git push = déploiement🐢 5-10 min manuelles
Expérience dev🌟 Preview, logs, Analytics🔧 Monitoring à configurer
Coût💸 20 $+/mois, plus cher avec le trafic💰 12 $/mois fixe (plusieurs projets)
Scalabilité📈 Auto-scaling📊 Ajustement manuel
Contrôle⚠️ Limité par la plateforme✅ Contrôle total
Cas d’usageProjets entreprise, services globauxProjets perso, petites équipes, budget limité

Conseil final :

  • Développeur solo avec plusieurs side projects : l’auto-hébergement économise beaucoup
  • Équipe sans ops ou trafic très variable : restez sur Vercel
  • Pas de bon ou mauvais choix technique — seulement ce qui convient

Les fichiers de config complets et plus de détails sont sur ce dépôt GitHub (placeholder — remplacez par le vôtre). Des questions ? Commentez — évitez que d’autres retombent dans les mêmes pièges.

Déploiement complet Next.js Docker en auto-hébergement

Étapes complètes de la config standalone au déploiement, avec proxy inverse et correctif rendu streaming

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Configurer le mode Standalone

    Activer le mode standalone dans next.config.js :

    1. Ouvrir next.config.js
    2. Ajouter : output: 'standalone'
    3. Lancer le build : npm run build
    4. Vérifier : le dossier .next/standalone est généré

    Points clés :
    • Le mode standalone ne copie pas automatiquement public et .next/static
    • Ces deux dossiers doivent être copiés manuellement dans le Dockerfile
    • Sinon toutes les ressources statiques renvoient 404

    Exemple :
    ```javascript
    const nextConfig = {
    output: 'standalone',
    }
    module.exports = nextConfig
    ```
  2. 2

    Step 2: Créer un Dockerfile multi-étapes

    Dockerfile en trois phases :

    Phase 1 - Installation des dépendances :
    • Image de base node:20-alpine
    • Copier uniquement package.json et package-lock.json
    • npm ci (cache Docker)

    Phase 2 - Build de l'application :
    • Copier node_modules depuis la phase 1
    • Copier tout le code source
    • npm run build

    Phase 3 - Exécution en production :
    • Créer un utilisateur non root (sécurité)
    • Copier le dossier public (ressources statiques)
    • Copier la sortie .next/standalone
    • Copier .next/static (CSS/JS compilés)
    • HOSTNAME="0.0.0.0" et PORT=3000
    • Commande : node server.js

    Points clés :
    • Trois phases : image de 1,5 Go à 200 Mo
    • Copier public et .next/static, sinon 404
    • Utilisateur non root pour la sécurité
  3. 3

    Step 3: Configurer le proxy inverse Nginx

    Proxy inverse Nginx avec buffering désactivé :

    1. Installer Nginx (ou Caddy)
    2. Configurer le certificat SSL (Let's Encrypt)
    3. Créer le fichier de config Nginx

    Config clé (obligatoire) :
    • proxy_buffering off;
    • proxy_cache off;
    • proxy_set_header X-Accel-Buffering no;

    En-têtes requis :
    • proxy_set_header Host $host;
    • proxy_set_header X-Real-IP $remote_addr;
    • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    • proxy_set_header X-Forwarded-Proto $scheme;

    WebSocket (si besoin) :
    • proxy_set_header Upgrade $http_upgrade;
    • proxy_set_header Connection "upgrade";

    Test :
    ```bash
    nginx -t
    nginx -s reload
    ```

    Attention : sans désactiver le buffering, le rendu streaming échoue
  4. 4

    Step 4: Gérer les variables d'environnement

    Distinguer variables de build et de runtime :

    Build (préfixe NEXT_PUBLIC_) :
    • Passer lors du docker build
    • --build-arg
    • Dockerfile : ARG NEXT_PUBLIC_API_URL
    • ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

    Runtime (côté serveur) :
    • docker run -e
    • ou docker-compose.yml
    • Lire via process.env

    Mode Standalone :
    • runtimeConfig ne fonctionne pas
    • Utiliser App Router pour les variables
    • Côté serveur : const dbUrl = process.env.DATABASE_URL

    Exemple :
    ```bash
    # Build
    docker build --build-arg NEXT_PUBLIC_API_URL="https://api.example.com" .

    # Runtime
    docker run -e DATABASE_URL="postgres://..." your-image
    ```
  5. 5

    Step 5: Corriger le rendu streaming

    Résoudre l'échec du rendu streaming (chat IA, SSE, etc.) :

    Symptômes :
    • Sortie streaming inactive, tout arrive d'un coup après longue attente
    • Server-Sent Events sans push temps réel

    Solution 1 - Config Nginx (obligatoire) :
    • proxy_buffering off
    • X-Accel-Buffering: no
    • Redémarrer Nginx

    Solution 2 - Edge Runtime :
    • En tête de la route API : export const runtime = 'edge'
    • Optimisé pour les réponses streaming
    • Plus stable en environnement Docker

    Vérification :
    ```bash
    curl -N http://yourdomain.com/api/chat \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"message": "Hello"}'
    ```
    Avec -N, sortie ligne par ligne attendue

    Autres vérifications :
    • Proxy Cloudflare : nuage orange = buffering (désactiver ou Pro)
    • Health check Docker : peut perturber le streaming
    • Load balancer : config séparée si présent
  6. 6

    Step 6: Déployer et valider

    Construire l'image et déployer :

    1. Build de l'image Docker :
    ```bash
    docker build -t nextjs-app .
    ```

    2. Lancer le conteneur :
    ```bash
    docker run -d \
    -p 3000:3000 \
    -e DATABASE_URL="postgres://..." \
    -e API_KEY="xxx" \
    --name nextjs-app \
    nextjs-app
    ```

    3. Valider le déploiement :
    • État du conteneur : docker ps
    • Logs : docker logs nextjs-app
    • Accès : curl http://localhost:3000
    • Ressources statiques : chemin /_next/static/

    4. Configurer Nginx et redémarrer :
    • Proxy inverse correct
    • Test HTTPS
    • Vérifier le rendu streaming

    5. Surveillance et maintenance :
    • Redémarrage auto : --restart unless-stopped
    • Consulter les logs régulièrement
    • Surveiller les ressources serveur

    Dépannage courant :
    • Ressources statiques 404 : vérifier copie public et .next/static
    • Variables inactives : distinguer build et runtime
    • Conteneur inaccessible : HOSTNAME=0.0.0.0

FAQ

Combien économise-t-on en auto-hébergement ?
Vercel : 35-50 $/mois (variable selon le trafic). Docker auto-hébergé : 12 $/mois fixe (plusieurs projets possibles). Économie : 25-40 $/mois, 300-500 $/an. Coût fixe, sans explosion en cas de pic. Adapté aux projets perso, petites équipes, budgets serrés.
Pourquoi les ressources statiques renvoient 404 ?
Le mode standalone ne copie pas public et .next/static. Correction : dans le Dockerfile, phase runner :
• COPY --from=builder /app/public ./public
• COPY --from=builder /app/.next/static ./.next/static
Si 404 persiste, vérifier les permissions avec --chown=nextjs:nodejs.
Le rendu streaming ne fonctionne pas, que faire ?
99 % des cas : buffering du proxy inverse. Corrections :
1) Nginx : proxy_buffering off; proxy_set_header X-Accel-Buffering no;
2) Edge Runtime sur la route API : export const runtime = 'edge'
3) Proxy Cloudflare : désactiver le nuage orange ou passer Pro
4) Test : curl -N, sortie ligne par ligne attendue
Les variables d'environnement ne fonctionnent pas ?
Distinguer build et runtime :
• NEXT_PUBLIC_* : --build-arg au docker build, ARG et ENV dans le Dockerfile
• Runtime : docker run -e ou docker-compose.yml, lecture via process.env
• En mode Standalone, runtimeConfig ne fonctionne pas ; utiliser App Router
L'image Docker est trop volumineuse ?
Build multi-étapes :
• Phase 1 : dépendances uniquement (cache Docker)
• Phase 2 : build de l'app
• Phase 3 : fichiers runtime uniquement (standalone, public, static)
• Image finale : 1,5 Go → 200 Mo
• node:20-alpine pour réduire encore
Quand auto-héberger vs Vercel ?
Auto-hébergement : bases Linux/Docker, trafic stable, budget serré, déploiement manuel acceptable, projets perso/petites équipes.
Vercel : pas de compétences ops, trafic très variable, réseau edge mondial, fonctionnalités Vercel (Analytics, Edge Config), budget confortable, efficacité dev prioritaire.
Le conteneur démarre mais reste inaccessible ?
Vérifier :
1) HOSTNAME=0.0.0.0 (pas 127.0.0.1), ENV HOSTNAME="0.0.0.0" dans le Dockerfile
2) Mapping de ports : docker run -p 3000:3000
3) Conteneur actif : docker ps
4) Logs : docker logs <container-id>
5) Test interne : docker exec -it <container-id> wget -O- http://localhost:3000

11 min de lecture · Publié le: 20 déc. 2025 · Mis à jour le: 30 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog