Live · status OK
Retour au blog
Développement12 min

Next.js static export et Core Web Vitals : le playbook 2026

TL;DR

Pour atteindre 95+ PageSpeed mobile sur Next.js static export : self-host les fonts via raw @font-face (pas next/font/local), preload la font LCP avec fetchPriority=high, éviter Framer Motion sur above-the-fold, utiliser uniquement transform/opacity pour les animations, réserver l'espace layout pour le contenu i18n, servir via Cloudflare free tier.

Julien Daniel
ParJulien Daniel
Founder & CTO, OptionWeb
Partager
Dashboard PageSpeed Insights avec scores de performance élevés

Après 12 mois de données production sur 40+ sites Next.js construits en static export (output: 'export'), voici le playbook exact qu'on applique pour atteindre 95+ PageSpeed mobile. Pas de théorie — que ce qui marche mesurablement.

Baseline : ce que static export donne gratuitement

Avec output: 'export' + Vercel ou hébergeur standard, un site Next.js vanilla score déjà bien car tout le HTML est pré-rendu, TTFB faible, pas de cold start, code splitting automatique par route. Un site Next.js 15 static export vierge score 95-100 mobile. Dès qu'on ajoute Framer Motion, Google Fonts, icônes, analytics, on tombe à 70-85 sans s'en rendre compte.

LCP : la bataille contre le render-blocking CSS

Les rapports PageSpeed sur nos sites Next.js montrent systématiquement le même problème : deux fichiers CSS bloquants (un Tailwind, un next/font Google déclarations) qui ajoutent ~500ms de render delay sur mobile throttled. C'est 100% du budget LCP.

1. Supprimer le CSS render-blocking des fonts

Le plus gros gain : remplacer next/font/google pour la font display par une déclaration @font-face raw pointant une URL stable dans public/fonts/.

styles/globals.csscss
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin.woff2') format('woff2');
  size-adjust: 102%;
  ascent-override: 95%;
}

Stratégie font qui marche vraiment

Notre stack standard 2026 :

  • Display font (headings)1 variable font chargée via raw @font-face, range 500-700, preload fetchPriority=high. Typique : Space Grotesk, Inter, Geist.
  • Body fontnext/font/google Inter avec 2 weights (400, 600), subset latin, adjustFontFallback=Arial. Auto-preload par Next.js.
  • Monospace (code blocks)next/font/google JetBrains Mono, 1 weight (400), preload=false.

INP : pièges Framer Motion à éviter

INP a remplacé FID en mars 2024 et est bien plus strict. Au-dessus de 200ms, Google considère 'poor'. Framer Motion est souvent le coupable.

Piège 1 : framer-motion au mount initial

tsx
// ❌ Mauvais: délai LCP
<motion.h1 initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
  Notre titre
</motion.h1>

// ✅ Bon: CSS keyframe, zéro délai JS
<h1 className="ow-anim-fade-up">Notre titre</h1>

Piège 2 : animer des propriétés layout

Ne jamais animer width, height, top, left, margin. Chaque frame force un reflow complet. Utiliser transform et opacity exclusivement — compositor-only, pas de layout.

Piège 3 : trop d'animations simultanées

Règle : 1-2 animations max par viewport visible. Utiliser whileInView avec once: true.

Piège 4 : pas de useReducedMotion

Le hook useReducedMotion de Framer Motion permet de skip les animations si prefers-reduced-motion est set. Accessibilité ET performance.

CLS : le pattern de réservation i18n

Sur sites i18n, le CLS apparaît quand les traductions chargent après le render initial (react-i18next TranslationProvider hydrate côté client). Un container vide au first paint se remplit soudainement avec 3-4 lignes, poussant 80-120px.

tsx
<div
  className="min-h-[280px] sm:min-h-[220px] md:min-h-[160px] lg:min-h-[130px]"
  data-speakable="true"
>
  <p>{t('tldr.content')}</p>
</div>

Images : AVIF, priority, fetchPriority

next.config.jsjs
images: {
  unoptimized: true, // obligatoire pour output: 'export'
  formats: ['image/avif', 'image/webp'],
}

En static export, unoptimized: true est obligatoire. Il faut pré-générer les variants AVIF + WebP au build via un script sharp.

Hosting : Brotli 11, HTTP/3, Early Hints

Peu importe l'optimisation du code, le hosting détermine les 10-15 derniers points de PageSpeed. Stack recommandée 2026 :

  1. Host originn'importe quel host statique (Cloudways, Netlify, Vercel, Cloudflare Pages, S3).
  2. Cloudflare free tier en frontHTTP/3 QUIC, Brotli 11, edge cache, protection DDoS, 30 min de setup.
  3. Early Hints (103) activésdashboard Cloudflare. Envoie preload hints avant réponse origin — élimine le wait TTFB du critical path.
  4. Cache rule_next/static/* → Cache Everything, Edge TTL 1 an. Hashed names = immutable-safe.
  5. Purge cache on deploywrangler pages deployment ou Cloudflare API dans CI.

Comment débugger une régression PageSpeed

Méthode pour identifier la cause en 10 minutes :

  1. Comparer rapports PageSpeedouvrir last-known-good et régressé côte à côte. Noter quelle métrique a drop.
  2. Check critical request chainvoir ce qui bloque le render qui ne bloquait pas avant.
  3. Chrome DevTools Performancelocalhost avec throttling 4x CPU + Fast 3G. Timeline main thread.
  4. Git diff depsajout npm package ? Packages lourds (Framer Motion, lodash, moment) ajoutent First Load JS.
  5. Check next build outputcomparer route sizes avec last deploy. Route +20KB = suspect.
Tags#nextjs#performance#core-web-vitals#pagespeed#framer-motion