Pourquoi self-héberger Nuxt sur un VPS ?
Avec Nitro, Nuxt 3 génère un build portable (preset: node-server) qui démarre comme un simple serveur Node écoutant sur un port. Cette portabilité est idéale pour un VPS : vous bénéficiez du rendu côté serveur, des routes server/api et du cache de route sans dépendre d'un runtime edge propriétaire.
L'autohébergement supprime les quotas de fonctions et vous laisse régler la mise en cache, la compression et le nombre d'instances Node. Pour une application Vue 3 en production avec authentification, appels API serveur et SEO soigné, le VPS offre la stabilité d'un serveur persistant et un coût fixe, sans surprise de facturation à l'invocation.
Bénéfices concrets d'un Nuxt autohébergé
- Routes serveur
server/apisans facturation par appel. - SSR et hydratation Vue 3 servis depuis un serveur Node persistant et stable.
- Build Nitro portable (
node-server) facile à conteneuriser et à reproduire. - Cache de route et règles
routeRules(SSR, SSG, ISR, SWR) maîtrisés côté serveur. - Mutualisation de plusieurs apps Nuxt sur un même VPS derrière Nginx.
- Variables d'environnement runtime (
runtimeConfig) gérées en clair sur votre serveur. - Contrôle total sur les en-têtes HTTP (CSP, HSTS, cache-control) via le middleware Nitro.
Prérequis matériels et logiciels
Comme pour tout framework moderne, le build Nuxt consomme de la mémoire : visez 2 Go de RAM minimum (le build peut être réalisé en CI si votre VPS est plus modeste). Au runtime, 1 vCPU et 1 Go suffisent pour le serveur Nitro sous charge légère ; montez à 2 vCPU / 2 Go pour des applications avec de nombreuses routes server/api simultanées.
Installez Node.js 20 LTS via nvm ou une image node:20-alpine, ainsi que PM2 pour la supervision de processus. Configurez nitro: { preset: 'node-server' } dans nuxt.config.ts. Un domaine pointé vers votre VPS est requis pour le certificat TLS. Ubuntu 22.04 ou 24.04 LTS reste la base recommandée.
Déployer Nuxt étape par étape
Configurer le projet pour Node
Définissez le preset Nitro dans
nuxt.config.ts:export default defineNuxtConfig({ nitro: { preset: 'node-server' } })Déclarez vos secrets dans
runtimeConfigcôté serveur uniquement — jamais dansruntimeConfig.public. Sur le VPS, installez Node.js 20 LTS vianvm install 20 && nvm use 20 && nvm alias default 20, puis PM2 globalement :npm install -g pm2.Builder l'application
Clonez le dépôt et installez les dépendances :
git clone https://git.votredomaine.com/votre-org/votre-app.git /srv/nuxt-app cd /srv/nuxt-app npm ci npm run buildNitro produit un dossier
.outputautonome contenantserver/index.mjset les assets statiques dans.output/public. Ce dossier est tout ce dont le VPS a besoin pour servir l'application en production — vous pouvez aussi l'assembler en CI et le transférer par rsync pour éviter le build sur le serveur.Créer le fichier d'environnement
Créez
/srv/nuxt-app/.envavec vos variables runtime. Toutes les variables préfixéesNUXT_écrasent les clés correspondantes deruntimeConfigau démarrage :NUXT_MY_SECRET_KEY=valeur-en-prod NITRO_HOST=0.0.0.0 NITRO_PORT=3000Ne commitez jamais ce fichier. Assurez-vous qu'il appartient à l'utilisateur qui fait tourner PM2 et qu'il est lisible uniquement par cet utilisateur (
chmod 600).Lancer le serveur Nitro avec PM2
Démarrez l'application et configurez le démarrage automatique :
pm2 start /srv/nuxt-app/.output/server/index.mjs \ --name nuxt-app \ --env-file /srv/nuxt-app/.env pm2 save pm2 startupLa commande
pm2 startupaffiche une ligne à copier-coller pour créer le service systemd. Exécutez-la avec les droits administrateur. Vérifiez que l'application est bien en ligne :pm2 statusdoit afficher l'étatonline.Configurer Nginx en reverse proxy
Créez
/etc/nginx/sites-available/nuxt-app:server { listen 80; server_name votredomaine.com www.votredomaine.com; # Assets hashés : cache long, servis directement location /_nuxt/ { root /srv/nuxt-app/.output/public; expires 1y; add_header Cache-Control "public, immutable"; } # Fichiers statiques publics location /favicon.ico { root /srv/nuxt-app/.output/public; } # Tout le reste → Nitro location / { proxy_pass http://127.0.0.1:3000; 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; } }Activez le site et testez la configuration :
ln -s /etc/nginx/sites-available/nuxt-app /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx.Activer le HTTPS avec Let's Encrypt
Installez Certbot et le plugin Nginx, puis générez le certificat :
apt install certbot python3-certbot-nginx certbot --nginx -d votredomaine.com -d www.votredomaine.comCertbot modifie automatiquement le bloc Nginx pour écouter sur le port 443 et forcer la redirection HTTP → HTTPS. Vérifiez que
X-Forwarded-Protoest bien transmis à Nitro — sans cela,useRequestHeaders()côté serveur et les redirections basées sur le protocole peuvent se tromper. Le renouvellement automatique est géré par le timer systemd de Certbot, vérifiable avecsystemctl status certbot.timer.Mettre à jour sans interruption de service
Pour une mise à jour, le rechargement PM2 (pas le redémarrage) garantit la continuité :
cd /srv/nuxt-app git pull npm ci npm run build pm2 reload nuxt-appPM2 lance la nouvelle version, attend qu'elle réponde, puis coupe l'ancienne : zéro downtime. Pour un rollback, revenez sur le commit précédent, rebuil et rechargez. Si le build se fait en CI, transférez uniquement le dossier
.outputpar rsync et rechargez sans reconstruire sur le serveur.
Optimiser le rendu avec routeRules
L'une des forces de Nuxt sur VPS est la granularité des stratégies de rendu page par page, sans changer d'infrastructure. Dans nuxt.config.ts, les routeRules permettent de mélanger SSR, SSG, ISR et SWR sur un seul déploiement Nitro.
Les pages statiques (blog, mentions légales, landing pages) peuvent être prégénérées au build avec prerender: true : Nitro les sert depuis le disque, sans Node. Les pages semi-dynamiques (listes de produits, tableaux de bord publics) profitent de swr: 60 pour un cache d'une minute invalidé en arrière-plan. Le SSR complet reste réservé aux pages personnalisées qui dépendent de la session ou du profil utilisateur.
Cette approche réduit la charge sur Node sans nécessiter un CDN payant ou un layer de cache externe — votre Nginx met en cache ce que Nitro prégénère.
Nitro server vs Docker vs Vercel/Netlify pour héberger Nuxt
Faites défiler le tableau
| Approche | Avantages | Contraintes |
|---|---|---|
| Nitro `node-server` + PM2 | Simple, léger, rechargement sans downtime, coût fixe VPS | Supervision à gérer, un seul serveur sans haute dispo native |
| Docker sur VPS | Isolation, reproductibilité, rollback par image, compose multi-apps | Image à bâtir et stocker, mémoire supplémentaire par conteneur |
| Vercel / Netlify | Zéro configuration, edge CDN mondial, preview par PR | Quotas de fonctions, coût variable, pas de contrôle sur l'infra |
| Kamal (déploiement zero-downtime) | Orchestre Docker + proxy Traefik, git push suffit | Ruby requis, config initiale plus longue, adapté aux équipes |
Dépannage : erreurs courantes en production
Error: listen EADDRINUSE: address already in use :::3000
Un processus tient déjà le port 3000. Identifiez-le avec ss -tlnp | grep 3000 et arrêtez l'instance PM2 orpheline : pm2 delete nuxt-app suivi d'un redémarrage propre. Évitez de lancer plusieurs instances sans load balancer — configurez plutôt le cluster_mode de PM2.
502 Bad Gateway dans Nginx
Nitro n'est pas démarré ou écoute sur le mauvais port. Vérifiez pm2 status et consultez les logs : pm2 logs nuxt-app --lines 50. Vérifiez aussi que la directive proxy_pass dans Nginx pointe bien sur http://127.0.0.1:3000 (et non localhost qui peut résoudre en IPv6 si le process écoute uniquement en IPv4).
useRuntimeConfig() retourne undefined côté serveur
Les variables NUXT_* ne sont lues qu'au démarrage du processus Nitro. Si vous modifiez .env après le lancement, rechargez : pm2 reload nuxt-app. Assurez-vous d'utiliser --env-file au démarrage et non d'exporter les variables dans le shell courant.
Les routes server/api répondent 404 en production
Vérifiez que votre build inclut bien le dossier server/ dans .output. Un nitro.preset mal configuré (par exemple static au lieu de node-server) génère un site statique sans serveur. Reconstruisez avec npm run build après avoir vérifié nuxt.config.ts.
X-Forwarded-Proto ignoré, redirections en boucle
Si Nuxt redirige HTTP → HTTPS en boucle, c'est que le middleware de redirection voit http malgré le HTTPS côté client. Assurez-vous que Nginx transmet bien proxy_set_header X-Forwarded-Proto $scheme et que votre nuxt.config.ts ne force pas la redirection HTTPS en doublon avec Certbot.
Supervision et métriques avec PM2
Au-delà du simple pm2 status, PM2 expose un tableau de bord temps réel avec pm2 monit : CPU, mémoire, relances et logs des processus. Pour une application Nuxt sous charge, activez le mode cluster pour utiliser tous les vCPU disponibles :
pm2 start .output/server/index.mjs --name nuxt-app -i maxPM2 distribue automatiquement les requêtes entre les workers. Associez PM2 à un outil comme Grafana via pm2-prometheus pour des alertes sur les redémarrages intempestifs — signe d'un bug ou d'une fuite mémoire à corriger avant qu'il n'affecte les utilisateurs.