Guide de déploiement

Déployer une application Next.js sur un VPS

Déployer sur un VPS Cloud →

Tutoriel

Déployer une application Next.js sur un VPS

Développement13 min de lecture6 étapes

Next.js combine rendu côté serveur, génération statique et API routes dans un seul framework React. Le déployer sur un VPS plutôt que sur une plateforme propriétaire vous libère des quotas de fonctions, des limites de bande passante et du vendor lock-in, tout en gardant le SSR pleinement opérationnel.

Sommaire· Pourquoi self-héberger Next.js sur un VPS ?1/14
  1. 01Pourquoi self-héberger Next.js sur un VPS ?
  2. 02Bénéfices concrets d'un Next.js auto-hébergé
  3. 03Prérequis matériels et logiciels
  4. 04Build Docker multi-stage : node:20-alpine
  5. 05Variables d'environnement : .env.local vs .env.production
  6. 06Déployer Next.js étape par étape
  7. 07Configuration Nginx complète avec upstream
  8. 08Stratégie ISR : revalidation et persistance du cache
  9. 09Server Components vs Client Components : impact RAM et CPU
  10. 10CI/CD : GitHub Actions ou Forgejo
  11. 11Monitoring avec PM2
  12. 12Dépannage : erreurs courantes en production
  13. 13Sauvegarder le cache .next/cache
  14. 14Déployer Next.js en un clic depuis le Marketplace

Pourquoi self-héberger Next.js sur un VPS ?

Next.js est souvent associé à une plateforme d'hébergement spécifique, mais son serveur Node.js standalone tourne parfaitement sur n'importe quel VPS. L'auto-hébergement devient pertinent dès que vous utilisez intensivement le SSR, l'ISR (régénération statique incrémentale) ou les routes API : ces fonctionnalités consomment des invocations facturées sur les plateformes managées, alors qu'elles sont gratuites et sans quota imposées sur votre serveur. Vous contrôlez le cache ISR sur disque, la bande passante des images optimisées et le temps d'exécution des fonctions, sans limite de 10 secondes. Pour une agence qui héberge plusieurs sites clients, un VPS mutualise les coûts et simplifie la facturation.

Bénéfices concrets d'un Next.js auto-hébergé

  • SSR et API routes sans quota d'invocations ni facturation à la fonction.
  • Cache ISR persistant sur disque, pas de revalidation perdue entre déploiements.
  • Bande passante incluse, idéale pour les sites riches en images et vidéos.
  • Plusieurs projets Next.js sur un même VPS, mutualisés sous Nginx.
  • Optimisation d'images next/image servie localement sans surcoût par transformation.
  • Build et déploiement maîtrisés via Git, CI/CD ou simple git pull et rebuild.

Prérequis matériels et logiciels

Le build de Next.js est gourmand en mémoire : prévoyez au moins 2 Go de RAM (4 Go pour un gros projet avec beaucoup de pages), sinon le build peut échouer par manque de mémoire. Côté runtime, 1 à 2 vCPU suffisent pour servir le SSR. Installez Node.js 18 ou 20 LTS, soit nativement avec nvm, soit via une image Docker node:20-alpine. Utilisez PM2 pour superviser le processus Node, ou Docker pour l'isolation. Un domaine pointé vers le VPS est nécessaire pour le SSL. Activez output: 'standalone' dans next.config.js pour un déploiement léger.

Build Docker multi-stage : node:20-alpine

Un build Docker multi-stage réduit l'image finale à l'essentiel et évite d'embarquer les outils de compilation en production. La stratégie en trois étapes — deps, builder, runner — est la plus courante pour Next.js en mode standalone.

# Étape 1 — dépendances
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Étape 2 — build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Étape 3 — runner (image finale)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

L'étape deps installe les dépendances de production uniquement (--omit=dev). L'étape builder copie tout le source et lance npm run build avec l'accès aux modules. L'étape runner ne contient que le dossier standalone généré par Next.js, les assets statiques et le dossier public — aucun node_modules source, aucun fichier de configuration de build. L'image finale pèse souvent deux à trois fois moins qu'une image naïve.

Variables d'environnement : .env.local vs .env.production

Next.js distingue deux familles de variables selon leur portée.

Variables publiques (NEXT_PUBLIC_*) : elles sont intégrées au bundle JavaScript au moment du build et exposées au navigateur. Elles conviennent à l'URL d'une API publique, à un identifiant Google Analytics, à un flag de fonctionnalité. Ne jamais y placer un secret.

Variables serveur : lues uniquement côté Node (API routes, Server Components, getServerSideProps). Elles ne sont jamais envoyées au client. C'est ici que vivent les clés d'API tierces, la chaîne de connexion à la base de données, les secrets JWT.

# .env.local — développement local (jamais committé)
NEXT_PUBLIC_API_URL=http://localhost:4000
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb

# .env.production — valeurs de production (jamais committé non plus)
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://user:[email protected]:5432/mydb

Sur le VPS, deux approches coexistent : injecter les variables dans l'environnement du processus PM2 via son fichier d'écosystème (ecosystem.config.js), ou les passer à Docker via --env-file .env.production. La seconde est préférable : le fichier reste sur le disque du serveur, hors du dépôt git, et Docker le monte au démarrage du conteneur sans l'exposer dans les logs.

// ecosystem.config.js (PM2)
module.exports = {
  apps: [{
    name: 'nextjs',
    script: '.next/standalone/server.js',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000
    }
  }]
};

À noter : les variables NEXT_PUBLIC_* doivent être connues au moment du build, pas seulement au démarrage. Si vous changez une variable publique après le build, il faut reconstruire l'application — changer uniquement l'environnement du processus ne suffira pas.

Déployer Next.js étape par étape

  1. Préparer le serveur

    En SSH, installez Node.js 20 LTS et PM2 (npm install -g pm2), ou Docker. Clonez le dépôt et créez .env.production avec vos variables (NEXT_PUBLIC_* pour le client, secrets serveur pour les API routes).

  2. Builder l'application

    Lancez npm ci puis npm run build. Avec output: 'standalone', Next.js génère un dossier .next/standalone autonome contenant uniquement les dépendances nécessaires, ce qui allège fortement l'image.

  3. Démarrer le serveur Node

    Lancez le serveur avec pm2 start node --name nextjs -- .next/standalone/server.js sur le port 3000, ou via un conteneur Docker. Configurez pm2 startup et pm2 save pour un redémarrage automatique au reboot du VPS.

  4. Configurer Nginx en frontal

    Créez un bloc serveur qui fait proxy_pass http://localhost:3000, transmet les en-têtes Host et X-Forwarded-For, et sert directement /_next/static/ depuis le disque pour soulager Node. Activez la compression gzip.

  5. Installer le certificat SSL

    Obtenez un certificat Let's Encrypt via Certbot pour votredomaine.com, forcez la redirection HTTPS et configurez le renouvellement automatique. Vérifiez que les en-têtes X-Forwarded-Proto sont bien transmis pour le SSR.

  6. Mettre en place les déploiements

    Automatisez le cycle git pull && npm ci && npm run build && pm2 reload nextjs via un script ou un webhook Git. Le reload PM2 assure un redémarrage sans coupure (zero-downtime) entre deux versions.

Configuration Nginx complète avec upstream

Un bloc Nginx complet pour Next.js dépasse le proxy_pass minimal. Il faut gérer les assets statiques directement sur disque, les en-têtes de cache, la compression et la transmission correcte des informations client.

upstream nextjs_upstream {
  server 127.0.0.1:3000;
  keepalive 64;
}

server {
  listen 80;
  server_name votredomaine.com www.votredomaine.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  server_name votredomaine.com www.votredomaine.com;

  ssl_certificate     /etc/letsencrypt/live/votredomaine.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/votredomaine.com/privkey.pem;

  gzip on;
  gzip_types text/plain text/css application/javascript application/json image/svg+xml;

  # Assets statiques Next.js — cache long durée
  location /_next/static/ {
    alias /home/deploy/monapp/.next/static/;
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  # Fichiers publics (images, favicons…)
  location /public/ {
    alias /home/deploy/monapp/public/;
    expires 30d;
  }

  # Tout le reste vers Next.js
  location / {
    proxy_pass http://nextjs_upstream;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    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;
    proxy_cache_bypass $http_upgrade;
  }
}

Le bloc upstream avec keepalive 64 maintient un pool de connexions persistantes vers Node, ce qui évite la négociation TCP à chaque requête. La directive proxy_http_version 1.1 est obligatoire pour que les connexions keepalive fonctionnent effectivement. Les assets _next/static reçoivent Cache-Control: immutable car Next.js y place des hashes de contenu — le fichier ne change jamais à URL égale.

Stratégie ISR : revalidation et persistance du cache

L'ISR (Incremental Static Regeneration) permet à Next.js de régénérer une page statique en arrière-plan après un délai donné, sans rebuild complet. Sur un VPS, cela nécessite que le cache survive aux redémarrages.

// app/produits/[id]/page.tsx (App Router)
export const revalidate = 3600; // régénère toutes les heures

// pages/produits/[id].tsx (Pages Router)
export async function getStaticProps() {
  return {
    props: { ... },
    revalidate: 3600
  };
}

Sur le VPS, le cache ISR est stocké dans .next/cache. Pour qu'il survive aux redéploiements, deux options existent.

Option 1 — volume persistant : montez .next/cache en dehors du dossier de déploiement. Lors d'un git pull et rebuild, copiez l'ancien cache avant d'écraser .next puis restaurez-le. Le script de déploiement ressemble à :

cp -r .next/cache /tmp/nextjs-cache-backup
npm run build
cp -r /tmp/nextjs-cache-backup .next/cache
pm2 reload nextjs

Option 2 — cache handler Redis : pour plusieurs instances Node ou un besoin de partage entre machines, Next.js supporte les cache handlers personnalisés depuis la version 13.4. Cela permet d'externaliser le cache ISR vers Redis, ce qui garantit la cohérence entre instances.

Si votre trafic est faible, l'option 1 suffit. L'option 2 devient nécessaire dès que vous avez plusieurs processus Node (cluster PM2) ou plusieurs VPS derrière un load balancer.

Server Components vs Client Components : impact RAM et CPU

Depuis Next.js 13 et l'App Router, les composants sont Server Components par défaut. La distinction a des conséquences directes sur la consommation de ressources de votre VPS.

Server Components s'exécutent uniquement côté serveur. Ils peuvent lire la base de données directement, accéder au système de fichiers, et ne sont jamais envoyés au bundle JavaScript du navigateur. Le résultat est du HTML pur, ce qui réduit la taille du bundle client et améliore le Core Web Vitals (LCP). Sur le serveur, ils consomment du CPU à chaque requête SSR non mise en cache.

Client Components ('use client' en tête de fichier) sont hydratés dans le navigateur. Ils sont nécessaires pour les interactions utilisateur : événements, état local, hooks (useState, useEffect). Ils augmentent la taille du bundle JavaScript envoyé au client.

// Server Component — s'exécute sur le serveur, jamais envoyé au client
async function ProductList() {
  const products = await db.query('SELECT * FROM products LIMIT 20');
  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

// Client Component — hydraté dans le navigateur
'use client';
import { useState } from 'react';
function AddToCart({ productId }: { productId: number }) {
  const [added, setAdded] = useState(false);
  return <button onClick={() => setAdded(true)}>{added ? 'Ajouté' : 'Ajouter'}</button>;
}

Sur un VPS, la règle pratique est : garder les Server Components pour tout ce qui touche aux données, et limiter les Client Components aux zones d'interaction. Un composant rendu 10 000 fois par heure côté serveur consomme du CPU ; le même rendu côté client consomme la RAM du navigateur du visiteur, pas votre serveur.

CI/CD : GitHub Actions ou Forgejo

Automatiser le déploiement évite les erreurs humaines et garantit que chaque merge sur la branche principale déclenche un rebuild propre. Deux solutions courantes sur VPS : GitHub Actions (si votre dépôt est sur GitHub) et Forgejo (forge auto-hébergée, alternative open source à GitHub/Gitea).

GitHub Actions — workflow de déploiement via SSH :

name: Deploy Next.js
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: deploy
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            cd /home/deploy/monapp
            git pull origin main
            npm ci
            npm run build
            pm2 reload nextjs

Forgejo — si vous auto-hébergez votre forge sur le même VPS ou un serveur dédié, utilisez un runner Forgejo Act. La syntaxe des workflows est identique à GitHub Actions, ce qui facilite la migration.

Dans les deux cas, les secrets (clé SSH, variables d'environnement) se configurent dans les secrets du dépôt et ne transitent jamais dans les logs. Pour les builds lourds (projet avec beaucoup de pages), envisagez de builder l'image Docker sur le runner CI et de la pousser dans un registre privé, puis de la puller sur le VPS — cela évite de consommer la RAM du serveur de production pendant le build.

Monitoring avec PM2

PM2 est à la fois le gestionnaire de processus et le premier outil de monitoring de votre application Next.js. Quelques commandes essentielles.

# État des processus
pm2 list

# Logs en temps réel
pm2 logs nextjs

# Métriques CPU et RAM
pm2 monit

# Redémarrage sans coupure (zero-downtime)
pm2 reload nextjs

# Redémarrage complet (coupure courte)
pm2 restart nextjs

PM2 peut aussi journaliser les métriques dans un fichier et les exporter vers des systèmes externes. Pour un monitoring plus poussé, activez le module pm2-logrotate pour éviter que les fichiers de log ne saturent le disque :

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 7

En production, sauvegardez la liste des processus après chaque changement : pm2 save. Cette liste est rechargée automatiquement au démarrage du système si vous avez exécuté pm2 startup et validé la commande qu'il vous fournit.

Si vous utilisez l'ISR, montez un volume persistant pour le dossier .next/cache afin que les pages revalidées survivent aux redéploiements. Sans cela, chaque rebuild repart d'un cache vide et provoque un pic de génération à la volée. Pour plusieurs instances Node derrière un load balancer, externalisez ce cache vers un stockage partagé ou Redis avec un cache handler personnalisé.

Dépannage : erreurs courantes en production

Trois à cinq erreurs reviennent systématiquement lors du premier déploiement d'une application Next.js sur VPS.

ENOMEM pendant npm run build
Le build Next.js compile en parallèle et peut dépasser 2 Go de RAM sur un projet moyen. Si le build s'interrompt avec Killed ou JavaScript heap out of memory, augmentez la limite mémoire de Node :

NODE_OPTIONS="--max-old-space-size=4096" npm run build

Alternativement, passez par le build Docker multi-stage décrit plus haut : le build s'effectue dans le conteneur et seule l'image finale (légère) est copiée sur le VPS.

Port 3000 already in use
Un ancien processus PM2 ou un processus Node orphelin écoute déjà sur le port 3000. Identifiez-le et arrêtez-le :

lsof -i :3000
kill -9 <PID>
# Ou, si c'est PM2 qui a déjà le processus :
pm2 delete nextjs
pm2 start ecosystem.config.js --env production

MODULE_NOT_FOUND en production avec output: 'standalone'
Le dossier .next/standalone contient une copie des dépendances nécessaires, mais pas les assets statiques ni le dossier public. Si vous copiez uniquement standalone, ces ressources manquent et le serveur plante au démarrage. Copiez les trois dossiers :

cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public

Application lente après redémarrage
Le premier chargement d'une page SSR après un redémarrage de PM2 est plus lent : le moteur V8 doit recompiler le code JavaScript (JIT). Pour réchauffer l'application automatiquement après le démarrage, ajoutez un script de warmup qui appelle les routes principales :

pm2 reload nextjs && sleep 5 && curl -sf https://votredomaine.com > /dev/null

Erreur X-Forwarded-Proto et boucle de redirection HTTPS
Si Next.js détecte que la requête est en HTTP alors qu'elle est déjà passée par Nginx en HTTPS, il peut créer une boucle de redirection. Assurez-vous que Nginx transmet bien X-Forwarded-Proto: https et que votre application lit cet en-tête pour déterminer le protocole réel.

Sauvegarder le cache .next/cache

Le cache .next/cache contient deux types de données précieuses : les pages ISR revalidées et le cache de compilation Webpack/SWC. Perdre ce cache oblige Next.js à régénérer toutes les pages ISR à la volée dès le premier hit, ce qui peut créer un pic de charge si votre site a beaucoup de pages statiques.

Mettez en place une sauvegarde simple avec rsync ou tar avant chaque déploiement :

#!/bin/bash
APP_DIR=/home/deploy/monapp
BACKUP_DIR=/home/deploy/backups

# Sauvegarde du cache avant le rebuild
tar -czf $BACKUP_DIR/nextjs-cache-$(date +%Y%m%d-%H%M%S).tar.gz $APP_DIR/.next/cache

# Nettoyage des sauvegardes de plus de 7 jours
find $BACKUP_DIR -name 'nextjs-cache-*.tar.gz' -mtime +7 -delete

# Déploiement
cd $APP_DIR
git pull origin main
npm ci
npm run build

# Restauration du cache de compilation (pas ISR)
CACHE_ARCHIVE=$(ls -t $BACKUP_DIR/nextjs-cache-*.tar.gz | head -1)
tar -xzf $CACHE_ARCHIVE $APP_DIR/.next/cache/webpack -C /

pm2 reload nextjs

Le cache Webpack accélère les rebuilds suivants de manière significative — sur un projet moyen, un rebuild avec cache complet prend 40 à 60 % moins de temps qu'un build à froid.

Déployer Next.js en un clic depuis le Marketplace

Le Marketplace ServOrbit propose un template Next.js Stack qui configure automatiquement Node.js LTS, PM2, Nginx et PostgreSQL sur votre VPS. En quelques minutes, votre environnement de production est prêt à recevoir votre application React SSR — sans configuration manuelle.

Comparé à l'installation décrite dans ce guide, le template gère la mise en place initiale et vous permet de démarrer directement à l'étape « déployer votre code ».

Votre environnement Next.js prêt en quelques minutes

Le template Next.js Stack configure automatiquement Node.js LTS, PM2, Nginx et PostgreSQL — prêt à recevoir votre application React SSR.

Besoin d'aide ?

Parcourez notre centre d'aide et notre FAQ, ou contactez notre équipe — rappel, WhatsApp ou e-mail. Support en français, anglais et arabe.

Écrire sur WhatsApps'ouvre dans un nouvel onglet