Retour au blog

L’export statique de Next.js (output: 'export') : le guide complet

L’export statique de Next.js (output: 'export') : le guide complet

10 octobre 2026 — Next.js

Publié le

10 octobre 2026

Temps de lecture

4 min de lecture

Catégorie

Next.js

Mots-clés

  • Next.js
  • Static Export
  • App Router
  • Hosting

(Intro)Comment fonctionne l’export statique de Next.js avec l’App Router, ce qu’il ne sait pas faire, et les solutions de contournement pour les images, les routes dynamiques, les formulaires et les redirections.

Un site Next.js exporté en statique, c’est simplement du HTML, du CSS, du JavaScript et des ressources dans un dossier. Vous pouvez déposer ce dossier sur n’importe quel hébergeur statique — Cloudflare Pages, GitHub Pages, S3, Netlify, un serveur Nginx — sans serveur Node.js à faire tourner, à mettre à jour ou à payer. Ce site est construit exactement ainsi.

Ce guide explique comment fonctionne l’export statique dans l’App Router, ce à quoi vous renoncez, et comment contourner chaque limite.

L’activer

Depuis Next.js 14, next export a disparu ; l’export statique est une option de configuration :

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  // Optional: /about → /about/index.html instead of /about.html
  trailingSlash: true,
  images: { unoptimized: true },
}

export default nextConfig

next build écrit désormais le site dans out/. Prévisualisez-le avec n’importe quel serveur statique :

bash
npx serve out

Ce qui fonctionne

Plus qu’on ne le croit :

  • Les Server Components. Ils s’exécutent une seule fois, au build. Récupérer des données, lire des fichiers, appeler des API : le résultat est figé dans le HTML.
  • Les Client Components, les hooks, le context et toutes les bibliothèques côté client.
  • Les routes dynamiques, à condition de lister chaque chemin avec generateStaticParams.
  • Les Route Handlers en GET, qui deviennent des fichiers statiques (pratique pour feed.json, sitemap.xml, robots.txt).
  • L’API Metadata : titres, descriptions, Open Graph, URL canoniques.
  • La navigation côté client avec <Link> et le préchargement.

Ce qui ne fonctionne pas, et quoi faire à la place

Fonctionnalité Pourquoi ça échoue Solution
Optimisation d’images par défaut Nécessite un serveur images.unoptimized: true, images pré-dimensionnées ou loader personnalisé (Cloudinary, imgix, Cloudflare Images)
Routes dynamiques sans generateStaticParams Les pages doivent être connues au build Lister tous les paramètres ; définir dynamicParams = false
cookies(), headers(), données de requête Aucune requête à l’exécution Les lire côté client, ou déplacer la logique vers une API
Server Actions Nécessitent un serveur Envoyer un POST à une fonction serverless (Workers, Lambda)
Middleware Nécessite un serveur Règles au niveau de l’hébergeur : _redirects, _headers, fonctions edge
redirects, rewrites, headers dans la config Appliqués par le serveur Next Idem : les configurer chez l’hébergeur
ISR / revalidate Pas de serveur pour régénérer Reconstruire à chaque changement de contenu (push Git ou webhook)
Routage i18n intégré Fonctionnalité du Pages Router Segments de langue : app/[lang]/... avec generateStaticParams

Des routes dynamiques bien faites

Chaque segment dynamique a besoin de la liste de ses valeurs au moment du build :

app/blog/[slug]/page.tsx
import { getAllPosts, getPost } from '@/lib/blog'
import { notFound } from 'next/navigation'

export const dynamicParams = false // unknown slugs are a 404, not a runtime render

export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await getPost(slug)
  if (!post) notFound()
  return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}

Notez que dans Next.js 15, params est une Promise : pensez à l’await.

Un piège : si generateStaticParams renvoie un tableau vide, le build échoue, car la route ne produirait aucune page. Si une collection peut être vide (un nouveau blog, par exemple), renvoyez un paramètre factice et laissez-le afficher une 404.

Les images

Vous avez trois options raisonnables :

  1. unoptimized: true, en exportant vous-même les images à la bonne taille (WebP ou AVIF, 1 à 2 fois la largeur affichée). Simple et rapide si vous êtes rigoureux.
  2. Un loader personnalisé pointant vers un CDN d’images, qui renvoie des variantes redimensionnées à la demande :
image-loader.js
export default function loader({ src, width, quality }) {
  return `https://images.example.com/${src}?w=${width}&q=${quality || 75}`
}
next.config.mjs
const nextConfig = {
  output: 'export',
  images: { loader: 'custom', loaderFile: './image-loader.js' },
}
  1. Un simple <img> avec srcset, loading="lazy" et des width/height explicites pour éviter les décalages de mise en page.

Formulaires et tout ce qui est côté serveur

Un site statique peut tout à fait avoir des formulaires, une recherche et des connexions : la partie serveur vit simplement ailleurs. Le schéma habituel, c’est une petite fonction serverless que la page appelle avec fetch. J’ai détaillé celle de ce site dans un formulaire de contact serverless avec Cloudflare Workers et D1.

Redirections et en-têtes

Configurez-les chez l’hébergeur. Sur Cloudflare Pages et Netlify, un fichier _redirects placé dans public/ est copié dans out/ :

public/_redirects
/old-page    /new-page    301
/blog/*      /journal/:splat    301

Et un fichier _headers pour les en-têtes de cache et de sécurité :

public/_headers
/_next/static/*
  Cache-Control: public, max-age=31536000, immutable

Mettre à jour le contenu sans serveur

Comme il n’y a pas d’ISR, chaque changement de contenu demande un nouveau build. C’est moins contraignant qu’il n’y paraît : connectez l’hébergeur à votre dépôt Git, et chaque push construit et déploie en une ou deux minutes. Pour un contenu géré dans un CMS, faites pointer le webhook du CMS vers le hook de déploiement de l’hébergeur. Pour un blog, garder les articles en Markdown dans le dépôt reste l’option la plus simple — voir un blog Markdown avec Next.js sans CMS.

Quand l’export statique n’est pas le bon choix

  • Des pages qui dépendent de l’utilisateur connecté, rendues côté serveur.
  • Des milliers de pages qui changent toutes les quelques minutes (le temps de build augmente avec le nombre de pages).
  • Tout ce qui exige vraiment un rendu à la requête, comme des prix personnalisés.

Pour les sites vitrines, portfolios, documentations, blogs et pages d’atterrissage, l’export statique est généralement l’option la plus rapide, la moins chère et la plus robuste qui soit.

Check-list

  • output: 'export' dans next.config
  • Chaque route dynamique a generateStaticParams et dynamicParams = false
  • Images gérées (unoptimized, loader personnalisé ou <img>)
  • Pas de cookies(), headers(), Server Actions ni middleware
  • Redirections et en-têtes configurés chez l’hébergeur
  • Déploiements déclenchés par les push Git ou les webhooks du CMS
  • out/ prévisualisé en local avant le premier déploiement

Étape suivante : faites en sorte que les moteurs de recherche comprennent les pages exportées — le SEO technique pour l’App Router de Next.js.

Newsletter

Recevez les nouveaux articles par e-mail

Un court e-mail à chaque nouvelle publication. Pas de spam, et désinscription possible à tout moment.

SEO technique pour l’App Router de Next.js : une check-list pratique
SEO technique pour l’App Router de Next.js : une check-list pratique
Article suivant