
Je suis Mathieu Ponton, Co-Founder & ingénieur logiciel chez Apogée Consult à Lyon. Ingénieur diplômé de Polytech Lyon (Informatique), j'ai fait trois ans en apprentissage, partagés entre la Métropole de Lyon (inclusion numérique avec Res'in et sobriété énergétique avec Écolyo) et Superwyze, une startup medtech (POCs, dont certains aujourd'hui industrialisés, et travail sur des codebases existantes). J'ai livré plus de 10 projets en production (web, mobile et IA / RAG) pour des PME, startups et organisations publiques.
Optimisation des fonts : notre setup minimal qui élimine le FOUT sans 200 ko de WOFF2
- perf
Le FOUT n'est pas une fatalité, et le charger avec un gros fichier WOFF2 n'est pas la seule solution. Voici notre configuration next/font en production.
Optimisation des fonts : notre setup minimal qui élimine le FOUT sans 200 ko de WOFF2
Le flash de texte non stylé, FOUT, apparaît quand le navigateur affiche le texte avec une police de substitution avant que la fonte personnalisée soit chargée. Le flash de texte invisible, FOIT, fait l'inverse : le texte est caché jusqu'au chargement. Les deux dégradent l'expérience. La solution naïve est de tout précharger. C'est rarement la bonne.
Pourquoi font-display: swap seul ne suffit pas
La propriété font-display: swap est souvent présentée comme la solution au FOIT : elle force le navigateur à afficher le texte avec la police de substitution immédiatement, puis à faire le swap quand la fonte arrive. Le FOIT disparaît, mais le FOUT reste, et il peut provoquer un Cumulative Layout Shift si les métriques des deux polices divergent.
La cause du CLS lié aux fonts n't est pas font-display, c'est la différence de métriques entre la fonte de substitution et la fonte cible. Inter et Arial ont des hauteurs de ligne et des largeurs de caractère différentes. Quand le swap se produit, le texte "saute".
La vraie solution combine deux choses : un font-display adapté et l'ajustement des métriques de la fonte de substitution via les descripteurs size-adjust, ascent-override, descent-override, et line-gap-override.
Le setup next/font en production
next/font gère l'essentiel automatiquement pour les Google Fonts et les fonts locales. Voici la configuration que nous utilisons :
// lib/fonts.ts
import { Inter } from "next/font/google";
export const inter = Inter({
subsets: ["latin"],
display: "swap",
variable: "--font-sans",
preload: true,
fallback: ["system-ui", "Arial"],
adjustFontFallback: true, // ajustement automatique des métriques
});Le paramètre adjustFontFallback: true est la clé. Next.js calcule automatiquement les descripteurs CSS (size-adjust, ascent-override, etc.) pour que la fonte de substitution soit métriquement proche de la fonte cible. Le CLS lié aux fonts passe généralement à zéro.
Application dans le layout :
// app/layout.tsx
import { inter } from "@/lib/fonts";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr" className={inter.variable}>
<body className="font-sans">{children}</body>
</html>
);
}Le subsetting : ce qui réduit vraiment le poids
Un fichier WOFF2 complet pour Inter couvre l'ensemble des scripts latins, cyrilliques, grecs, et les symboles mathématiques. Pour un site entièrement en français, vous n'avez besoin que du subset latin, qui couvre les 128 caractères ASCII de base plus les diacritiques courants.
Différence de taille pour Inter Regular :
- Fichier WOFF2 complet : environ 100 ko
- Subset
latinuniquement : environ 18 ko
next/font/google applique automatiquement le subsetting quand vous déclarez subsets: ["latin"]. Google Fonts sert alors uniquement les glyphes nécessaires. Le fichier est téléchargé et mis en cache localement par Next.js au moment du build, l'utilisateur ne fait jamais de requête vers fonts.googleapis.com.
Pour des fonts auto-hébergées, le subsetting nécessite un outil dédié. Nous utilisons pyftsubset (partie de fonttools) en CI :
pyftsubset Inter-Regular.ttf \
--output-file=Inter-Regular-latin.woff2 \
--flavor=woff2 \
--unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"Les variable fonts : un fichier, toutes les graisses
Les variable fonts permettent de servir un seul fichier WOFF2 qui couvre l'ensemble des graisses (100 à 900) et éventuellement d'autres axes (italique, largeur). Sans variable font, vous chargez un fichier par graisse utilisée.
Inter est disponible en variable font. next/font la détecte automatiquement et sert la version variable quand c'est possible.
Comparaison pour Inter (Regular + Medium + SemiBold + Bold) :
- 4 fichiers WOFF2 subset latin : environ 72 ko total
- 1 fichier variable font subset latin : environ 35 ko
Le gain est significatif. La contrepartie : les navigateurs très anciens (IE11, Edge Legacy) ne supportent pas les variable fonts. Si votre audience cible inclut ces navigateurs, un fallback statique est nécessaire.
Ce qu'on ne fait plus
Preload sur toutes les graisses. Précharger plusieurs fichiers de fonts en <link rel="preload"> consomme de la bande passante critique et retarde le First Contentful Paint. Nous préchargeons uniquement la graisse utilisée dans le fold visible, généralement Regular.
Inliner la font en base64. Certains tutorials recommandent d'inliner les fonts en base64 dans le CSS pour éliminer la requête réseau. Le fichier CSS résultant est massif, non mis en cache séparément, et bloquant au rendu. C'est la pire approche pour la performance.
Utiliser des fonts hébergées sur Google CDN en production. next/font télécharge et auto-héberge les fonts au build. En production, aucune requête vers fonts.googleapis.com ou fonts.gstatic.com n't est émise. C'est important pour la conformité RGPD (transfert de données vers Google) et pour la performance (aucune dépendance DNS externe).
La limite de cette approche
next/font fonctionne bien pour des fonts avec subset connu au build. Si votre application sert du contenu multilingue avec des scripts variables (arabe, japonais, thaï), le subsetting statique ne couvre pas tous les cas. Google Fonts gère ce cas via l'API unicode-range et le chargement conditionnel par bloc Unicode, mais cela nécessite de retomber sur les fonts servies par Google, avec les implications RGPD que ça implique.
Pour ces cas, une solution hybride, next/font pour la langue principale, Google Fonts avec crossOrigin et une politique de privacy adaptée pour les langues secondaires, est souvent le compromis raisonnable.