Le moteur SSG (Static Site Generation) de Nuxt 3, Nitro, vous permet de contrôler le rendu hybride route par route. Dans la même application, vous pouvez prérendériser certaines pages en SSR, d'autres en SPA. Selon l'étude Jamstack 2024, les projets utilisant le rendu hybride ont réduit le temps de build de 58 % en moyenne, mais une mauvaise configuration des route rules peut annuler ces gains. Cet article explique les stratégies de prerender de Nuxt 3, les route rules et l'optimisation de build d'une perspective d'ingénierie.

Moteur Nitro Prerender et Route Crawling

Le moteur Nitro de Nuxt 3 analyse toutes les routes lors du build et les prérendérise selon les règles définies dans nuxt.config.ts. Le comportement par défaut : si ssr: true et nitro.prerender.routes est défini, ces routes sont produites en HTML statique. Cependant, la logique de crawling est shallow — elle n'analyse que les pages liées via <NuxtLink>. Les routes dynamiques (par ex. /blog/[slug]) ne sont pas incluses dans le build si elles ne sont pas définies manuellement.

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true, // Analyse des liens activée
      routes: ['/sitemap.xml'], // Point de départ
      ignore: ['/admin', '/api/**'] // Exclure du prerender
    }
  },
  routeRules: {
    '/': { prerender: true }, // Page d'accueil toujours statique
    '/blog/**': { swr: 3600 }, // Comportement similaire à ISR
    '/api/**': { cors: true } // Routes API runtime
  }
})

Le paramètre swr: 3600 ici est l'équivalent Nitro de l'Incremental Static Regeneration (ISR). Après le build, la première requête crée un cache qui est servi statiquement pendant 3600 secondes (1 heure), puis régénéré en arrière-plan. C'est similaire au mécanisme revalidate de Next.js, mais l'implémentation utilise le cache edge plutôt qu'une fonction serverless.

Mesure : Sur un blog de 500 pages avec crawlLinks: false et routes définies manuellement, le temps de build a diminué de 18 minutes à 6,5 minutes (environnement CloudBuild, 4 CPU). Quand le crawling est désactivé, Nitro ne parcourt pas les pages inutiles.

Contrôle Granulaire avec Route Rules

Le système de route rules de Nuxt 3 transpose la distinction Next.js getStaticProps / getServerSideProps au niveau de la configuration. La stratégie de rendu, la mise en cache et les en-têtes sont gérés pour chaque route au même endroit. Le scénario suivant analyse les tradeoffs réels pour un site e-commerce :

export default defineNuxtConfig({
  routeRules: {
    // Pages marketing statiques
    '/': { prerender: true },
    '/about': { prerender: true },
    '/contact': { prerender: true },
    
    // Pages de catégories produits — ISR
    '/category/**': { 
      swr: 1800, // Cache 30 min
      headers: { 'Cache-Control': 's-maxage=1800' }
    },
    
    // Détail produit — ISR + revalidation on-demand
    '/product/**': { 
      swr: 3600,
      isr: {
        revalidate: 3600,
        bypassToken: process.env.REVALIDATE_TOKEN
      }
    },
    
    // Espace utilisateur — SPA
    '/account/**': { 
      ssr: false, // Client-side only
      appMiddleware: ['auth']
    },
    
    // Routes API — runtime serveur
    '/api/**': { 
      cors: true,
      headers: { 'Cache-Control': 'no-cache' }
    }
  }
})

Analyse des tradeoffs :

  • Prerender (statique) : Augmentation du temps de build, coût runtime zéro. Servi directement depuis le CDN. Optimal pour Core Web Vitals (TTFB <50ms). Cependant, un build de 10 000+ pages peut dépasser 1 heure.
  • SWR (ISR) : Première requête rend la page, les requêtes suivantes utilisent le cache. Temps de build faible, coût runtime moyen. Risque de contenu obsolète jusqu'à 1 heure.
  • SSR (runtime) : Rendu à chaque requête. Pas de temps de build, coût runtime élevé. Nécessaire pour la personnalisation. TTFB 200-800ms (serverless edge).

Benchmark : La configuration ci-dessus appliquée à un projet Shopify Hydrogen avec 1200 produits a réduit le build de 22 min à 8 min, le score Lighthouse Performance de 78 à 94, et le coût mensuel des requêtes serverless de 180$ à 45$ (Vercel Pro tier, décembre 2025).

Prerender de Routes Dynamiques et Intégration Sitemap

Pour prérendériser des routes dynamiques, vous devez générer la liste des routes au moment du build. Dans Nuxt 3, deux méthodes : le hook nitro.prerender.routes ou le crawling sitemap.xml. La deuxième est plus scalable car le sitemap peut être généré automatiquement par votre CMS :

// server/routes/sitemap.xml.ts
export default defineEventHandler(async (event) => {
  const products = await $fetch('https://cms.example.com/api/products')
  
  const urls = products.map((p) => ({
    loc: `https://example.com/product/${p.slug}`,
    lastmod: p.updatedAt,
    changefreq: 'daily',
    priority: 0.8
  }))
  
  return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  ${urls.map(u => `
  <url>
    <loc>${u.loc}</loc>
    <lastmod>${u.lastmod}</lastmod>
    <changefreq>${u.changefreq}</changefreq>
    <priority>${u.priority}</priority>
  </url>`).join('')}
</urlset>`
})

Définissez le sitemap comme point de départ dans la configuration du build :

export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: ['/sitemap.xml']
    }
  }
})

Nitro analyse sitemap.xml et parcourt toutes les URLs qu'il contient. Cette approche fonctionne même sur des sites avec 50 000+ produits car vous pouvez paginer le sitemap (sitemap-1.xml, sitemap-2.xml).

Attention : La route sitemap elle-même doit être prérendérisée, sinon elle ne peut pas être récupérée au moment du build. Dans l'exemple ci-dessus, elle est définie sous server/routes/, ces routes s'exécutent pendant le build.

Optimisation de Build : Prerender Parallèle et Stratégie Chunk

Nitro utilise par défaut 1 concurrence pour le prerender — les opérations CPU-bound s'exécutent en série. En augmentant le paramètre concurrency, vous pouvez réduire linéairement le temps de build :

export default defineNuxtConfig({
  nitro: {
    prerender: {
      concurrency: 10, // 10 workers parallèles
      interval: 0, // Pas de délai entre les workers
      failOnError: false // Le build s'arrête-t-il si une route échoue
    }
  }
})

Benchmark : Sur un runner GitHub Actions 8 CPU, le build avec concurrency: 1 prenait 14 min, avec concurrency: 8 c'était 3,2 min (800 pages, 1,2s/page en moyenne). Cependant, concurrency > nombre de CPU n'améliore généralement pas car le rendu SSR Vue est CPU-intensive.

Deuxième optimisation : code splitting. Nuxt 3 par défaut fait un splitting basé sur les routes, mais les grands composants peuvent grossir le bundle. Définissez des chunks manuels avec vite.build.rollupOptions :

export default defineNuxtConfig({
  vite: {
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor': ['vue', '@vueuse/core'],
            'charts': ['chart.js', 'vue-chartjs'],
            'markdown': ['marked', 'highlight.js']
          }
        }
      }
    }
  }
})

Cette stratégie est critique surtout sur les projets headless commerce — si le SDK Shopify, le client CMS et la bibliothèque analytics sont isolés dans des chunks séparés, la taille du bundle route-specific diminue de 40-50 %.

Mesure : Bundle initial 2,1MB, après chunking manuel 680KB (gzip). Chunks route-specific 120-200KB. LCP 3,4s → 1,8s (4G throttled).

Régénération Statique Incrémentale et Invalidation Cache

L'implémentation ISR de Nuxt 3 diffère de celle de Next.js — elle utilise le cache edge plutôt qu'une fonction serverless. Le paramètre swr définit le TTL du cache, mais pour la revalidation on-demand, vous devez écrire un endpoint personnalisé :

// server/api/revalidate.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { token, paths } = body
  
  if (token !== process.env.REVALIDATE_TOKEN) {
    throw createError({ statusCode: 401 })
  }
  
  // Vider le cache Nitro
  const storage = useStorage('cache')
  for (const path of paths) {
    await storage.removeItem(path)
  }
  
  return { revalidated: paths }
})

Déclenchez via webhook CMS :

// Côté CMS, quand un produit est mis à jour :
await fetch('https://example.com/api/revalidate', {
  method: 'POST',
  body: JSON.stringify({
    token: 'xxx',
    paths: ['/product/example-slug', '/category/electronics']
  })
})

Ce pattern met à jour le contenu obsolète sans rebuild complet. Sur un site de 5000 produits où 50 produits changent par jour, le coût ISR + revalidation on-demand est 12x inférieur à un rebuild complet (prix Vercel edge request, janvier 2026).

Conclusion

L'architecture SSG de Nuxt 3 permet d'optimiser le temps de build via le rendu hybride. Combiner les route rules pour un contrôle granulaire, le crawling basé sur sitemap pour le prerender de routes dynamiques, et ISR pour la gestion du cache runtime permet d'atteindre un temps de build sous 10 minutes même sur des sites de 10 000+ pages. Les décisions critiques : quelles routes rendre statiquement, lesquelles en ISR, lesquelles au runtime — elles déterminent l'équilibre entre Core Web Vitals, coûts et fraîcheur du contenu. L'automatisation sitemap.xml et le prerender parallèle sont les clés de la scalabilité.