
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.
LCP sous 1.5s sur un site marketing : les 6 arbitrages qu'on assume
- perf
Atteindre un LCP sous 1.5 secondes sur un site marketing ne demande pas de tout optimiser. Ça demande de savoir quoi sacrifier. Six décisions concrètes.
LCP sous 1.5s sur un site marketing : les 6 arbitrages qu'on assume
Sur le dernier site marketing qu'on a livré, le LCP mesuré en conditions lab est à 1.3s. En field data (CrUX), le 75e percentile est à 1.4s. Voici ce qu'on a fait, et surtout ce qu'on a accepté de ne pas faire.
Rappel : qu'est-ce qui fait le LCP
Le Largest Contentful Paint mesure le temps avant que l'élément visible le plus large de la page soit rendu. Sur un site marketing, c'est presque toujours l'image hero ou le titre principal de la page d'accueil.
La chaîne critique :
- Connexion TCP + TLS (si pas en cache)
- HTML initial
- Découverte de la ressource LCP
- Chargement de la ressource
- Rendu
Chaque maillon a son levier. On en a travaillé quatre, ignoré deux volontairement.
Arbitrage 1 : servir les images depuis le même domaine que l'HTML
On avait initialement les images sur un CDN externe (Cloudinary). Les images étaient optimisées, mais le navigateur devait établir une connexion DNS + TCP + TLS séparée pour le CDN avant de pouvoir télécharger l'image LCP.
On a mesuré le coût : entre 80 et 200 ms de connexion additionnelle selon l'opérateur mobile en France.
On a migré les images statiques du hero vers le même domaine, servies par Next.js avec next/image. Le CDN externe reste pour les images uploadées par les utilisateurs, pas pour les assets marketing critiques.
Gain mesuré : 90 ms en médiane sur mobile 4G simulé.
Arbitrage 2 : priority sur l'image LCP, et seulement celle-là
next/image expose une prop priority qui injecte un <link rel="preload"> dans le <head> et désactive le lazy loading.
L'erreur courante : mettre priority sur toutes les images above-the-fold. Ça crée de la contention, le navigateur précharge simultanément plusieurs images et retarde toutes. Le LCP se dégrade paradoxalement.
On met priority uniquement sur l'image qui sera l'élément LCP :
// Uniquement l'image hero, pas les logos partenaires
<Image
src="/hero.webp"
alt="..."
width={1440}
height={800}
priority
sizes="100vw"
/>Les autres images above-the-fold restent en lazy loading. Le navigateur les charge après les ressources critiques.
Arbitrage 3 : CSS critique inline, le reste différé
Sur un site Next.js avec Tailwind, le CSS global est chargé en tant que fichier séparé. Si ce fichier bloque le rendu, le LCP attend son chargement complet.
Next.js avec app/ injecte automatiquement le CSS nécessaire pour les composants du segment courant. On ne fait rien de spécial, c'est le comportement par défaut du App Router. Mais on a vérifié que le CSS global importé manuellement (globals.css) reste minimal : réinitialisation, variables CSS, polices. Aucun composant dans globals.css.
Ce qu'on a accepté de ne pas faire : l'extraction manuelle du CSS critique (above-the-fold CSS) et son inlining dans le <head>. C'est possible avec des outils comme critters, mais la maintenance est coûteuse sur un site qui évolue souvent.
Arbitrage 4 : polices avec font-display: swap et subset latin uniquement
On utilise Inter via next/font/google. La configuration par défaut charge le subset latin, ce qui convient pour un site en français.
// src/app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});font-display: swap signifie que le navigateur affiche d'abord le texte avec la police système, puis swape vers Inter quand elle est disponible. Le texte est lisible immédiatement, même si la police finale n'est pas encore chargée. L'inconvénient (FOUT, Flash of Unstyled Text) est accepté : on préfère un texte lisible rapidement à un texte invisible en attente de la police.
next/font auto-héberge les fichiers de polices, ce qui évite la connexion externe vers fonts.googleapis.com. Gain : 30 à 60 ms d'évitement de connexion tierce.
Arbitrage 5 : pas de JavaScript tiers above-the-fold
Les scripts d'analyse (analytics, heatmaps, chat widgets) ont un impact direct sur le LCP s'ils sont chargés avant le rendu de l'élément LCP.
On charge tous les scripts tiers en strategy="lazyOnload" via next/script :
// src/app/layout.tsx
import Script from 'next/script';
<Script
src="https://analytics.example.com/script.js"
strategy="lazyOnload"
/>lazyOnload attend que la page soit interactive avant de charger le script. L'analytics démarre quelques secondes après l'affichage, assez tôt pour capturer les interactions, assez tard pour ne pas peser sur le LCP.
Ce qu'on assume : on rate les métriques des utilisateurs qui quittent la page en moins de 3 secondes. C'est un compromis acceptable pour un site marketing dont l'objectif principal est de convertir ceux qui restent.
Arbitrage 6 : TTFB sous 200 ms via les Server Components cachés
Le Time To First Byte impacte directement le LCP : si le serveur met 800 ms à répondre, le LCP ne peut pas être sous 1s.
On utilise le cache de Next.js App Router avec revalidate sur les pages statiques :
// app/(frontend)/page.tsx
export const revalidate = 3600; // Cache 1 heure
export default async function HomePage() {
const payload = await getPayload({ config: configPromise });
const hero = await payload.findGlobal({ slug: 'homepage' });
return <HomeTemplate hero={hero} />;
}La page est rendue à la première requête et mise en cache 1 heure. Les requêtes suivantes reçoivent la réponse HTML en quelques millisecondes depuis le cache de Next.js.
TTFB mesuré depuis un serveur Railway en Europe Ouest : 45 ms en cache, 380 ms sans cache.
Ce qu'on n'a pas fait
On n'a pas implémenté de Service Worker pour le cache offline. La complexité opérationnelle (cache invalidation, stratégies) n'est pas justifiée pour un site marketing qui se met à jour régulièrement.
On n'a pas investigué le protocole HTTP/3. Notre infrastructure ne le supporte pas encore, et l'impact sur le LCP serait marginal comparé aux optimisations ci-dessus.
La question que ça soulève : est-ce qu'atteindre 1.5s en field data sur le 75e percentile suffit pour l'algorithme de Google, ou faut-il viser le 90e percentile pour sortir du seuil "poor" sur des connexions dégradées ?