Développement Web

Nuxt : rendu serveur (SSR) et déploiement de Vue

13 دقائق للقراءة

📍 Article principal du parcours : Vue 3 et la Composition API : le guide complet. Pour la vue d’ensemble, commencez par là.

Introduction

NoteFlux fonctionne, mais c’est une application entièrement rendue dans le navigateur : quand un robot d’indexation ou un aperçu de partage social demande la page, il reçoit une coquille HTML vide, remplie ensuite par JavaScript. Pour le référencement, le partage et la vitesse d’affichage perçue, on veut que le serveur renvoie déjà le HTML rempli. C’est le rendu côté serveur (SSR), et Nuxt est le framework officiel qui l’apporte à Vue. À la fin de ce tutoriel, NoteFlux sera rendu côté serveur, ses notes pré-affichées dans le HTML, et l’application déployée en ligne — soit sur un serveur Node, soit en pages statiques.

🎯 Ce que vous allez apprendre

  • Créer un projet Nuxt et comprendre sa structure de dossiers.
  • Distinguer le rendu côté serveur, le rendu côté client et la génération statique.
  • Récupérer des données de façon compatible SSR avec useFetch et useAsyncData.
  • Exposer une route serveur dans server/api.
  • Construire l’application et la lancer en production sur un serveur Node.
  • Générer une version statique pour un hébergement de fichiers simple.

🛠️ Ce que vous allez construire

Vous reconstruisez la page des notes de NoteFlux dans Nuxt : une route serveur fournit les notes, une page les récupère avec useFetch, et le serveur renvoie le HTML déjà rempli — vérifiable d’un clic droit « Afficher le code source ». Puis vous produisez deux artefacts de déploiement : un serveur Node prêt pour la production, et une version 100 % statique.

Prérequis

  • Node.js 20 ou plus récent (Nuxt recommande la version LTS active, Node 22 ou plus).
  • Connaître les composants Vue et la réactivité. Test express : si vous savez écrire un composant avec <script setup> et un computed, vous êtes prêt ; sinon, lisez d’abord le tutoriel sur la réactivité.
  • ⏱️ Temps estimé : environ 50 minutes.

Au moment d’écrire ces lignes, la version courante est Nuxt 4, sortie à l’été 2025. C’est une évolution de Nuxt 3, pas une rupture : les concepts — Nitro, auto-imports, useFetch — sont les mêmes, avec une organisation de projet plus claire. Le principal changement visible est le regroupement du code applicatif dans un dossier app/.

Étape 1 — Créer le projet et le démarrer

Nuxt fournit un outil d’amorçage qui met en place toute la structure. On crée le projet, on installe les dépendances, et on lance le serveur de développement. Contrairement à une application Vue pure servie par Vite seul, Nuxt démarre aussi un serveur — c’est lui qui rendra le HTML.

# crée un nouveau projet Nuxt nommé noteflux
npm create nuxt@latest noteflux
cd noteflux
npm run dev

Le terminal affiche une adresse locale (par défaut http://localhost:3000). Ouvrez-la : la page d’accueil Nuxt s’affiche. La différence avec un projet Vue classique est invisible à l’œil mais fondamentale : le HTML que vous recevez est généré par le serveur, pas assemblé après coup par le navigateur.

Point d’étape — Le serveur tourne sur le port 3000. Si le port est déjà pris, Nuxt en propose un autre ; notez-le. Une erreur de version de Node est le blocage le plus courant ici : vérifiez avec node -v.

Étape 2 — Comprendre la structure des dossiers

Quelques dossiers à conventions précises font tout le travail de Nuxt. Comprendre leur rôle évite des heures de tâtonnement. Dans Nuxt 4, le code de l’interface vit sous app/ ; le code serveur reste à la racine.

noteflux/
├─ app/
│  ├─ app.vue          # composant racine (optionnel si app/pages existe)
│  ├─ pages/           # routage par fichiers : chaque fichier = une route
│  ├─ components/      # composants auto-importés
│  ├─ composables/     # fonctions réactives auto-importées
│  └─ layouts/         # gabarits partagés entre pages
├─ server/
│  └─ api/             # routes serveur (API)
├─ public/             # fichiers servis tels quels
└─ nuxt.config.ts      # configuration

Deux conventions changent la vie. D’abord, le routage par fichiers : déposer app/pages/notes.vue crée automatiquement la route /notes, sans configuration de routeur. Ensuite, les auto-imports : les composants de app/components, les fonctions de app/composables et les API de Vue (ref, computed) sont disponibles partout sans ligne import. Dès qu’un dossier app/pages/ existe, le fichier app.vue devient facultatif et Nuxt affiche les pages via son composant <NuxtPage />.

Étape 3 — Exposer les notes par une route serveur

Dans une vraie application, les notes viennent d’une base de données ou d’une API. Nuxt permet d’écrire cette API dans le même projet, sous server/api. Un fichier y devient un point d’accès HTTP. Créons une route qui renvoie les notes.

// server/api/notes.get.js
export default defineEventHandler(() => {
  return [
    { id: 1, titre: 'Idées pour NoteFlux', favori: true },
    { id: 2, titre: 'Liste de courses', favori: false },
    { id: 3, titre: 'Plan de la semaine', favori: false },
  ]
})

Le suffixe .get indique que cette route répond aux requêtes GET. La fonction defineEventHandler est auto-importée par Nuxt. Cette route est désormais accessible à /api/notes : ouvrez cette adresse dans le navigateur, vous verrez le tableau JSON. C’est sur cette route que la page va se brancher.

Étape 4 — Récupérer les données côté serveur avec useFetch

Voici le cœur du SSR. Si l’on récupérait les notes avec un simple fetch dans le navigateur, le serveur renverrait une page vide, puis le client comblerait le trou — on perdrait tout l’intérêt du rendu serveur. Nuxt fournit useFetch : il récupère les données sur le serveur lors du premier rendu, les inclut dans le HTML envoyé, et évite que le client ne refasse la même requête à l’hydratation.

<script setup>
const { data: notes, status, error } = await useFetch('/api/notes')
</script>

<template>
  <h1>Mes notes</h1>
  <p v-if="status === 'pending'">Chargement…</p>
  <p v-else-if="error">Impossible de charger les notes.</p>
  <ul v-else>
    <li v-for="n in notes" :key="n.id">
      {{ n.titre }} <span v-if="n.favori">★</span>
    </li>
  </ul>
</template>

Placez ce composant dans app/pages/notes.vue. Visitez /notes, puis faites un clic droit « Afficher le code source de la page » : les titres des notes sont déjà dans le HTML. C’est la preuve du rendu côté serveur — un robot d’indexation les voit sans exécuter le moindre script. useFetch renvoie data, status et error, ce qui couvre proprement les trois états de l’affichage.

Point d’étape — Dans le code source de la page (et non l’inspecteur), vous devez lire « Idées pour NoteFlux ». Si le HTML source est vide et que le texte n’apparaît qu’avec JavaScript activé, c’est que vous avez utilisé un fetch brut au lieu de useFetch.

Pour des cas plus complexes — agréger plusieurs sources, brancher un CMS, transformer la réponse —, useAsyncData offre le même bénéfice SSR avec davantage de contrôle ; son premier argument sert de clé de cache. useFetch n’en est qu’un raccourci pour le cas courant d’un seul appel réseau.

Étape 5 — Choisir son mode de rendu

Nuxt rend côté serveur par défaut, mais ce n’est pas l’unique option. Trois stratégies, selon le besoin, se règlent dans nuxt.config.ts.

// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true, // rendu serveur (défaut) — meilleur pour le référencement
})

Avec ssr: true (le défaut), chaque requête est rendue par le serveur : idéal pour des contenus qui changent et doivent être indexés. Avec ssr: false, Nuxt produit une application monopage classique, rendue dans le navigateur : plus simple à héberger, mais sans le bénéfice SEO. Enfin, la génération statique pré-rend toutes les pages au moment du build : on obtient des fichiers HTML figés, parfaits pour un contenu stable et un hébergement minimal. Le choix se résume souvent ainsi : contenu dynamique à indexer → SSR ; tableau de bord privé → application monopage ; site de contenu stable → statique.

Étape 6 — Construire et lancer en production

La commande de build prépare l’application pour la production. Nuxt s’appuie sur Nitro, son moteur serveur, qui produit une sortie autonome dans le dossier .output. Le preset par défaut, node-server, génère un serveur Node prêt à l’emploi.

# construit l'application pour la production
npm run build

# lance le serveur de production (NODE_ENV=production est important)
NODE_ENV=production node .output/server/index.mjs

Le serveur démarre sur le port 3000. Vous le changez avec les variables d’environnement PORT (ou NITRO_PORT) et HOST. Définir NODE_ENV=production n’est pas optionnel : sans cela, des avertissements de développement polluent les journaux et la performance n’est pas optimale. En pratique, on place ce processus derrière un gestionnaire comme PM2 pour qu’il redémarre automatiquement, et derrière un serveur web (Nginx) qui gère le HTTPS et relaie vers le port 3000.

Point d’étape — Après npm run build, le dossier .output/server/index.mjs existe. Le lancer doit afficher « Listening on http://[::]:3000 » ou équivalent, et l’application répondre sur ce port.

Étape 7 — Générer une version statique

Si NoteFlux n’a pas besoin d’un serveur qui tourne en continu, la génération statique est plus simple et moins chère à héberger. Une seule commande pré-rend toutes les pages en HTML.

# pré-rend toutes les pages dans .output/public
npm run generate

Le résultat, dans .output/public, est un ensemble de fichiers statiques que n’importe quel hébergement de fichiers sert directement — y compris les offres gratuites de pages statiques. Nuxt génère aussi des pages de repli (200.html, 404.html) pour gérer les routes dynamiques et les erreurs. C’est la voie la plus économique quand le contenu ne change qu’au moment du déploiement.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
Le HTML source est vide fetch brut au lieu de useFetch Utiliser useFetch / useAsyncData pour le SSR
« Hydration mismatch » dans la console Le rendu serveur diffère du rendu client (date, aléatoire, contenu conditionnel) Rendre la sortie déterministe ; isoler le code client dans <ClientOnly>
Les notes se chargent deux fois Requête refaite à l’hydratation useFetch transmet déjà les données du serveur au client : ne pas refetcher
defineEventHandler is not defined Fichier hors de server/ Placer les routes API sous server/api
Avertissements de dev en production NODE_ENV non défini Lancer avec NODE_ENV=production

Réalités du terrain

Le choix du mode de rendu a un coût d’hébergement direct. Le SSR exige un processus Node qui tourne en permanence : il faut donc un petit serveur (un VPS modeste suffit largement pour NoteFlux) ou un service qui facture au temps d’exécution. La génération statique, elle, ne produit que des fichiers : on la sert pour quelques centimes, voire gratuitement, sur un hébergement de pages statiques. Quand le SSR n’apporte pas de bénéfice réel — typiquement un outil interne derrière authentification, qui n’a pas à être indexé —, le mode statique ou monopage économise un serveur entier. Pensez aussi à la bande passante : une version statique se met facilement derrière un cache, ce qui réduit la charge et les temps de réponse pour des utilisateurs éloignés du serveur. Nitro propose des presets de déploiement pour de nombreuses plateformes ; on les choisit avec nitro.preset dans la configuration ou la variable NITRO_PRESET au build.

✅ Récapitulatif

Vous avez rendu NoteFlux côté serveur avec Nuxt : une route server/api fournit les données, useFetch les récupère sans casser le SSR, et le HTML part déjà rempli. Vous savez choisir entre rendu serveur, application monopage et génération statique, construire un serveur Node de production, et produire une version statique pour un hébergement minimal. C’est l’aboutissement du parcours : de la réactivité d’un composant jusqu’à la mise en ligne d’une application complète.

🧾 Aide-mémoire

Élément Rôle
npm create nuxt@latest Créer un projet Nuxt
app/pages/ Routage par fichiers
server/api/*.get.js Route serveur (API)
useFetch Récupération de données compatible SSR
useAsyncData Idem, avec plus de contrôle et clé de cache
ssr: true / false Rendu serveur ou application monopage
npm run build + node .output/server/index.mjs Serveur Node de production
npm run generate Génération statique

💪 À vous de jouer

Ajoutez une page de détail app/pages/notes/[id].vue qui récupère une seule note via /api/notes/[id]. Indice : créez la route serveur server/api/notes/[id].get.js et lisez le paramètre avec getRouterParam(event, 'id').

Voir une solution
// server/api/notes/[id].get.js
export default defineEventHandler((event) => {
  const id = Number(getRouterParam(event, 'id'))
  const toutes = [
    { id: 1, titre: 'Idées pour NoteFlux' },
    { id: 2, titre: 'Liste de courses' },
  ]
  return toutes.find(n => n.id === id) || null
})

Côté page, const { data } = await useFetch('/api/notes/' + route.params.id) récupère la note, rendue elle aussi côté serveur.

Tutoriels frères

Pour aller plus loin

FAQ

Nuxt 3 ou Nuxt 4 ?
Nuxt 4 est la version courante et l’évolution directe de Nuxt 3 : mêmes concepts, organisation de projet plus claire (le dossier app/). Pour un nouveau projet, partez sur Nuxt 4. Le code et les composables présentés ici sont identiques d’une version à l’autre.

Le SSR est-il toujours nécessaire ?
Non. Il est précieux pour le référencement et l’aperçu de partage d’un contenu public. Pour un tableau de bord privé qui n’a pas à être indexé, une application monopage (ssr: false) est plus simple à héberger.

Quelle différence entre useFetch et $fetch ?
$fetch est l’utilitaire de requête de bas niveau, parfait pour une action déclenchée par l’utilisateur (envoyer un formulaire). useFetch l’enveloppe pour le rendu universel : il récupère sur le serveur et transmet le résultat au client sans double requête. Pour charger les données initiales d’une page, on utilise useFetch.

Peut-on utiliser Pinia avec Nuxt ?
Oui, via le module officiel, et il fonctionne en SSR : l’état initialisé sur le serveur est transmis au client. C’est la combinaison recommandée pour gérer l’état d’une application Nuxt.

Faut-il un serveur puissant ?
Pour une application de la taille de NoteFlux, un petit serveur suffit. Et si vous optez pour la génération statique, aucun serveur applicatif n’est nécessaire : un simple hébergement de fichiers fait l’affaire.

مشاركة