Changer le thème

Optimisation des performances frontend : guide Core Web Vitals pour viser le score parfait

Easton editorial illustration: component assembly loom

Lighthouse affiche seulement 60 points, et il faut atteindre 90 en une semaine. Ouvrez Chrome DevTools : LCP, FID, CLS — il faut comprendre ce que signifient ces métriques. Pour les développeurs frontend, l’optimisation des performances arrive souvent comme un travail supplémentaire, sans préavis.

J’ai beaucoup trébuché : lors de ma première optimisation, j’ai mis du lazy loading sur toutes les images — le score LCP a baissé. La grande image hero au-dessus de la ligne de flottaison ne doit surtout pas être lazy-loadée. Une autre fois, j’ai passé deux jours sur une stratégie de cache Service Worker pour gagner seulement 2 points. Cet article n’est pas un exposé théorique : c’est un guide pratique qui produit des résultats visibles en 2 semaines — quelles optimisations offrent le meilleur ROI, quels pièges éviter, et comment faire passer Lighthouse de 60 à 90+ par ordre de priorité.

70%+
Problèmes LCP
Proviennent des images
41%
Gain de compression
AVIF vs JPEG
2 sem.
Cycle d’optimisation
60 → 90+
Source: Données terrain

Chapitre 1 : Comprendre les trois Core Web Vitals

Ne vous laissez pas impressionner par ces acronymes anglais — en résumé : la page charge-t-elle vite, réagit-elle aux clics, le layout reste-t-il stable ?

Qu’est-ce que les Core Web Vitals ?

Google a introduit en 2020 trois métriques clés d’expérience utilisateur. Elles influencent non seulement l’UX, mais aussi directement le classement SEO. Même avec un excellent contenu, une mauvaise performance fait chuter le ranking. En mars 2024, une mise à jour majeure : l’INP remplace le FID comme métrique centrale. Si vous optimisez encore le FID, vous êtes en retard.

LCP — Largest Contentful Paint

Le LCP mesure la vitesse de chargement du contenu principal — en général la grande image hero, un titre ou une vidéo au-dessus de la ligne de flottaison. Les seuils :

  • Moins de 2,5 secondes : bon (vert)
  • 2,5–4 secondes : à améliorer (jaune)
  • Plus de 4 secondes : mauvais (rouge)
    J’aime comparer le LCP au service du plat principal au restaurant. Si vous attendez dix minutes sans voir arriver le plat, vous serez mécontent, non ?
    Chiffre concret : quand le temps de chargement passe de 3 à 5 secondes, le taux de rebond augmente de 38 %. Sur mobile, si le chargement dépasse 3 secondes, 53 % des utilisateurs partent. Un LCP bien optimisé peut augmenter la conversion de 7 à 15 %.
38%
Hausse du taux de rebond
Source: Temps de chargement 3 s → 5 s

INP — Interaction to Next Paint

C’est la nouvelle métrique depuis mars 2024, qui remplace officiellement le FID. L’INP mesure la réactivité de la page aux interactions — clics, saisie clavier, toucher — sur tout le cycle de réponse. Les seuils :

  • Moins de 200 ms : bon (vert)
  • 200–500 ms : à améliorer (jaune)
  • Plus de 500 ms : mauvais (rouge)
    Pourquoi Google a-t-il remplacé le FID ? Le FID ne mesure que le « First Input Delay », tandis que l’INP couvre tout le processus d’interaction. Comme au restaurant : le FID vérifie si le serveur vous a entendu ; l’INP mesure le temps entre la commande et l’arrivée du plat.
    Lors de mon premier audit, l’INP d’un projet était à 650 ms — un clic sur un bouton prenait plus d’une demi-seconde, pas étonnant que les utilisateurs trouvent la page saccadée. La cause : une intégration YouTube automatique ; après suppression, l’INP est descendu à 220 ms — la fluidité était nettement perceptible.

CLS — Cumulative Layout Shift

Le CLS mesure la stabilité visuelle de la page. Vous connaissez ce cas : vous allez cliquer sur un bouton, la page saute soudain — et vous touchez une pub à côté ? C’est le CLS. Les seuils :

  • Moins de 0,1 : bon (vert)
  • 0,1–0,25 : à améliorer (jaune)
  • Plus de 0,25 : mauvais (rouge)
    La première fois que j’ai vu un CLS à 0,5, je pensais que c’était « pas si grave » — en réalité, c’était déjà une zone rouge. Causes fréquentes : images sans dimensions, publicités insérées brusquement, flash de texte invisible (FOIT).

Chapitre 2 : Optimisation LCP — le meilleur ROI en performance

Pourquoi je place le LCP en premier ?

  • Impact immédiat : les utilisateurs le ressentent tout de suite
  • Plus grand levier : un LCP typique peut passer de 5 s à moins de 2 s
  • Solutions matures : optimisation d’images, CDN — tout est éprouvé
  • ROI maximal : peu d’effort, résultats rapides — le meilleur rapport qualité/prix

Processus complet d’optimisation Core Web Vitals

Solution complète LCP, INP et CLS — Lighthouse de 60 à 90+ en 2 semaines

Estimated time: PT2W

  1. 1

    Step 1: Priorité P0 : optimisation des images (LCP)

    Les images causent plus de 70 % des problèmes LCP — ROI le plus élevé :
  2. 2

    Step 2: • AVIF

    41 % de compression en plus (vs JPEG)
  3. 3

    Step 3: • WebP

    30 % de compression en plus (vs JPEG)
  4. 4

    Step 4: • Next.js

    <Image priority />
  5. 5

    Step 5: Cas réel

    image hero de 500 Ko à 120 Ko (AVIF), LCP de 4,2 s à 2,1 s.
  6. 6

    Step 6: Priorité P0 : scripts tiers différés (INP)

    Les scripts tiers sont le principal goulot d’étranglement INP :
  7. 7

    Step 7: Cas réel

    suppression de l’embed YouTube auto, INP de 650 ms à 220 ms.
  8. 8

    Step 8: Priorité P0 : dimensions des images (CLS)

    Le CLS est la métrique la plus facile à corriger :
  9. 9

    Step 9: Cas réel

    dimensions sur toutes les images, CLS de 0,35 à 0,05.
  10. 10

    Step 10: Priorité P1 : code splitting et ressources

    Code splitting et optimisation des ressources :
  11. 11

    Step 11: • React

    React.lazy()
  12. 12

    Step 12: • Vue

    defineAsyncComponent()
  13. 13

    Step 13: Cas réel

    back-office admin de 1,2 Mo à 200 Ko au first view, INP de 450 ms à 180 ms.
  14. 14

    Step 14: Priorité P1 : polices et serveur

    Polices et optimisation serveur :
  15. 15

    Step 15: Priorité P2 : optimisations avancées

    Optimisations avancées (ROI moyen, difficulté élevée) :

1. Optimisation des images (ROI : ⭐⭐⭐⭐⭐)

C’est le levier le plus important — plus de 70 % des problèmes LCP viennent des images. Je commence toujours par là.

a) Formats d’image modernes

Ne sous-estimez pas le format : passer du JPEG à l’AVIF peut économiser 300 Ko sur une seule image.

<!-- Option 1 : balise <picture>, le navigateur choisit le meilleur format -->
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero image"> <!-- fallback pour navigateurs anciens -->
</picture>
// Option 2 : optimisation automatique Next.js (recommandé)
import Image from 'next/image'
<Image
  src="/hero.jpg"
  width={1200}
  height={600}
  priority // Important : images LCP avec priority — pas de lazy loading !
/>

Chiffres concrets :

  • AVIF : 41 % de compression en plus vs JPEG
  • WebP : 30 % de compression en plus vs JPEG
  • Cas réel : page e-commerce — hero de 500 Ko à 120 Ko (AVIF), LCP de 4,2 s à 2,1 s — divisé par deux !

b) Lazy loading (sauf images LCP)

Piège classique : lazy-loader aussi l’image LCP — le score baisse alors.

<!-- ❌ Erreur : ne pas lazy-loader les images LCP ! -->
<img src="hero.jpg" loading="lazy">
<!-- ✅ Correct : eager au-dessus de la ligne de flottaison, lazy en dessous -->
<img src="hero.jpg" loading="eager"> <!-- image LCP -->
<img src="product1.jpg" loading="lazy"> <!-- images plus bas -->
<img src="product2.jpg" loading="lazy">

Règle : aucune image visible au premier écran ne doit être lazy-loadée. Le lazy loading sert aux images sous la ligne de flottaison.

c) Images responsives

Les utilisateurs mobile n’ont pas besoin des grandes images desktop — srcset économise jusqu’à 60 % de bande passante.

<img
  src="hero-800w.jpg"
  srcset="hero-400w.jpg 400w,
          hero-800w.jpg 800w,
          hero-1200w.jpg 1200w"
  sizes="(max-width: 600px) 400px,
         (max-width: 1000px) 800px,
         1200px"
  alt="Hero"
/>

Effet réel : sur mobile, charger une image 400w au lieu de 1200w améliore le LCP d’environ 1 seconde.

d) CDN images et preload

Un CDN n’est pas un luxe, c’est une nécessité. Aliyun OSS coûte quelques dizaines d’euros par an.

<!-- Preload des images critiques — le navigateur les charge en priorité -->
<link rel="preload" as="image" href="hero.jpg">
<!-- CDN images (conversion de format + compression automatiques) -->
<!-- Tencent COS, Aliyun OSS, Cloudflare Images le supportent -->
<img src="https://cdn.example.com/hero.jpg?x-oss-process=image/format,webp/quality,80">

e) Dimensions et compression

Outils recommandés :

  • TinyPNG : compression en ligne, simple et efficace
  • ImageOptim : incontournable sur Mac, compression par lot
  • Squoosh : de Google, support AVIF
    Règles de compression :
  • Images above-the-fold : qualité 80 % (différence visuelle minime)
  • Autres images : 70 % (largement suffisant)
  • Largeur : double de la maquette (écrans haute densité)

f) Éviter les gros Base64 inline

// ❌ Ne pas inline les grosses images (plus de 10 Ko)
// Augmente le volume HTML et retarde le rendu
const heroImage = 'data:image/jpeg;base64,/9j/4AAQSkZJRg...' // 500 Ko
// ✅ Base64 seulement pour petites icônes (moins de 10 Ko)
// ex. icône de chargement, SVG simples
const icon = 'data:image/svg+xml;base64,PHN2ZyB3aWR...' // 2 Ko

2. Optimiser le temps de réponse serveur (ROI : ⭐⭐⭐⭐)

a) Utiliser un CDN

Toutes les ressources statiques via CDN ; le HTML peut aussi être mis en cache (avec SSG/ISR). J’ai vu des TTFB passer de 200 ms à 50 ms — la différence se ressent immédiatement.

b) Rendu serveur (SSR) ou génération statique (SSG)

// Next.js — génération statique (préféré, meilleures perfs)
export async function getStaticProps() {
  const data = await fetchData()
  return {
    props: { data },
    revalidate: 60 // ISR : régénération toutes les 60 secondes
  }
}
// Ou rendu serveur (si les données doivent être en temps réel)
export async function getServerSideProps() {
  const data = await fetchData()
  return { props: { data } }
}

c) Optimisation des requêtes base de données

  • Ajouter des index (n’oubliez pas EXPLAIN)
  • Redis pour les données chaudes
  • Éviter les requêtes N+1 (JOIN ou DataLoader)

3. Optimisation du chargement des ressources (ROI : ⭐⭐⭐)

a) CSS critique inline

<!-- CSS critique above-the-fold inline dans <head> -->
<style>
  .hero {
    width: 100%;
    height: 600px;
    background: #f0f0f0;
  }
  .nav {
    position: fixed;
    top: 0;
    width: 100%;
  }
</style>
<!-- CSS non critique différé -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

b) Optimisation des polices

/* font-display contre le FOIT (Flash of Invisible Text) */
@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap; /* Afficher la police de secours tout de suite */
}
<!-- Preload des polices critiques -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

Chapitre 3 : Optimisation INP — des interactions plus fluides

Les scripts tiers sont le pire ennemi de l’INP ! Cas extrême : une page avec 12 scripts tiers — Google Analytics, Facebook Pixel, widget support, scripts pub… INP directement au-dessus de 800 ms.

1. Optimiser l’exécution JavaScript (ROI : ⭐⭐⭐⭐)

a) Code splitting

Le code splitting, ce n’est pas si compliqué : charger « à la demande » — le code d’une page n’est chargé que quand l’utilisateur y va.

// React — code splitting par route
import { lazy, Suspense } from 'react'
const Dashboard = lazy(() => import('./Dashboard'))
const Profile = lazy(() => import('./Profile'))
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  )
}
// Vue 3 — composants asynchrones
const Dashboard = defineAsyncComponent(() => import('./Dashboard.vue'))

Effet réel : back-office admin de 1,2 Mo à 200 Ko au first view, INP de 450 ms à 180 ms — ouverture plus de deux fois plus rapide.

b) Découper les longues tâches

Si le thread principal est bloqué plus de 50 ms, c’est une long task — l’INP grimpe.

// ❌ Long task qui bloque le thread principal
function processLargeData(data) {
  for (let i = 0; i < 10000; i++) {
    // Calcul lourd, 10 000 entrées d'un coup
    heavyCalculation(data[i])
  }
}
// ✅ Découper avec requestIdleCallback
function processLargeData(data) {
  let index = 0
  function processChunk() {
    const chunkSize = 100 // 100 entrées à la fois
    let count = 0
    while (index < data.length && count < chunkSize) {
      heavyCalculation(data[index])
      index++
      count++
    }
    if (index < data.length) {
      // Pas terminé — continuer au prochain idle
      requestIdleCallback(processChunk)
    }
  }
  requestIdleCallback(processChunk)
}

c) Utiliser des Web Workers

Déporter les calculs lourds dans un Worker, libérer le thread principal.

// worker.js
self.onmessage = (e) => {
  const result = complexCalculation(e.data) // calcul dans le Worker
  self.postMessage(result)
}
// main.js
const worker = new Worker('worker.js')
worker.postMessage(data)
worker.onmessage = (e) => {
  console.log('Result:', e.data)
  // Mettre à jour l'UI avec le résultat
}

2. Optimiser les scripts tiers (ROI : ⭐⭐⭐⭐⭐)

Les scripts tiers, c’est comme des invités à une fête — plus il y en a, plus c’est le chaos. Chacun veut des ressources.

a) Différer les scripts non critiques

<!-- ❌ Chargement bloquant (fige le rendu) -->
<script src="analytics.js"></script>
<!-- ✅ Après le chargement de la page -->
<script defer src="analytics.js"></script>
<!-- ✅ Ou délai manuel de 3 secondes (plus agressif) -->
<script>
  window.addEventListener('load', () => {
    setTimeout(() => {
      const script = document.createElement('script')
      script.src = 'analytics.js'
      document.body.appendChild(script)
    }, 3000) // Analytics après 3 s de navigation
  })
</script>

b) Pattern Facade pour composants lourds

Un embed YouTube pèse plus de 1 Mo — le charger directement plombe l’INP.

// Miniature YouTube légère, vidéo réelle au clic
function VideoFacade({ videoId }) {
  const [showVideo, setShowVideo] = useState(false)
  if (!showVideo) {
    return (
      <div
        className="video-facade"
        style={{
          backgroundImage: `url(https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg)`,
          cursor: 'pointer'
        }}
        onClick={() => setShowVideo(true)}
      >
        <button className="play-button">▶ Lire la vidéo</button>
      </div>
    )
  }
  return <iframe src={`https://www.youtube.com/embed/${videoId}`} />
}

Effet réel : suppression de l’embed YouTube auto, INP de 650 ms à 220 ms.

3. Optimiser la gestion des événements (ROI : ⭐⭐⭐)

a) Debounce et throttle

import { debounce, throttle } from 'lodash'
// Debounce pour la recherche (300 ms après la dernière frappe)
const handleSearch = debounce((value) => {
  fetchSearchResults(value)
}, 300)
// Throttle pour le scroll (max toutes les 100 ms)
const handleScroll = throttle(() => {
  updateScrollPosition()
}, 100)

b) Écouteurs passifs

// Meilleures perfs de scroll — le navigateur sait que preventDefault ne sera pas appelé
window.addEventListener('scroll', handleScroll, { passive: true })
window.addEventListener('touchmove', handleTouch, { passive: true })

Chapitre 4 : Optimisation CLS — éviter les sauts de layout

Le CLS, c’est comme quelqu’un qui pousse votre livre vers le haut pendant que vous lisez — vous devez retrouver la ligne. Bonne nouvelle : c’est la métrique la plus facile à corriger.

1. Dimensions pour images et vidéos (ROI : ⭐⭐⭐⭐⭐)

Images sans dimensions : première cause de CLS — et la plus simple à corriger.

<!-- ❌ Sans dimensions — le layout saute au chargement -->
<img src="photo.jpg" alt="Photo">
<!-- ✅ Avec attributs — le navigateur réserve l'espace à l'avance -->
<img src="photo.jpg" width="800" height="600" alt="Photo">
<!-- ✅ Ou CSS aspect-ratio (navigateurs modernes) -->
<style>
  img {
    width: 100%;
    aspect-ratio: 16 / 9;
  }
</style>

Cas réel : site d’actualités — dimensions sur toutes les images, CLS de 0,35 à 0,05. aussi simple que ça.

2. Optimiser le chargement des polices (ROI : ⭐⭐⭐⭐)

a) Utiliser font-display: swap

@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap; /* Éviter le FOIT (écran blanc pendant le chargement) */
}

b) Preload des polices critiques

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

c) Polices système ou Variable Font

/* Polices système — pas de téléchargement, latence nulle */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
/* Variable Font — un fichier pour tous les graisses (100–900) */
@font-face {
  font-family: 'Inter';
  src: url('Inter-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
}

3. Réserver l’espace pour le contenu dynamique (ROI : ⭐⭐⭐⭐)

a) Espace réservé pour la publicité

.ad-container {
  min-height: 250px; /* Hauteur pub réservée — pas de saut avant chargement */
  background: #f0f0f0; /* Fond placeholder */
}

b) Skeleton screen

Les skeletons ne servent pas qu’à « faire joli » — ils stabilisent le layout.

// Afficher un skeleton avant chargement — pas d'apparition brutale
function ProductCard({ loading, data }) {
  if (loading) {
    return (
      <div className="skeleton">
        <div className="skeleton-image" style={{ width: '100%', height: '200px', background: '#e0e0e0' }} />
        <div className="skeleton-title" style={{ width: '80%', height: '20px', background: '#e0e0e0', margin: '10px 0' }} />
        <div className="skeleton-price" style={{ width: '40%', height: '20px', background: '#e0e0e0' }} />
      </div>
    )
  }
  return (
    <div className="product">
      <img src={data.image} alt={data.title} />
      <h3>{data.title}</h3>
      <p>{data.price}</p>
    </div>
  )
}

4. Ne pas insérer de contenu au-dessus de l’existant (ROI : ⭐⭐⭐⭐⭐)

// ❌ Banner en haut — le contenu descend (CLS explose)
<div>
  {showBanner && <Banner />}
  <Content />
</div>
// ✅ position fixed — le layout reste stable
<div>
  {showBanner && <Banner style={{ position: 'fixed', top: 0, zIndex: 1000 }} />}
  <Content style={{ marginTop: showBanner ? '60px' : '0' }} />
</div>

5. Animer avec transform plutôt que top/left (ROI : ⭐⭐⭐)

J’ai vu des animations avec top pour « faire joli » — le CLS a explosé.

/* ❌ Déclenche un layout, CLS en hausse */
.element {
  position: relative;
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { top: -100px; } /* top déclenche un reflow */
  to { top: 0; }
}
/* ✅ transform — composite uniquement (accélération GPU) */
.element {
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { transform: translateY(-100px); }
  to { transform: translateY(0); }
}

Chapitre 5 : Mise en pratique — processus d’optimisation complet

Les priorités comptent — ne commencez pas par le SSR, optimisez d’abord les images. Lors de ma première passe, ajouter width/height aux images a seul rapporté 10 points — des points gratuits.

Matrice des priorités

OptimisationROIDifficultéPriorité
Dimensions des imagesTrès élevéTrès faibleP0
Conversion de formatTrès élevéFaibleP0
Images LCPTrès élevéFaibleP0
Scripts tiers différésTrès élevéFaibleP0
PolicesÉlevéFaibleP1
Code splittingÉlevéMoyenP1
CDNÉlevéFaibleP1
SSR/SSGMoyenÉlevéP2
Web WorkersFaibleÉlevéP3

Outils et commandes de mesure

# 1. Lighthouse (référence)
# Chrome DevTools > Lighthouse > Generate report
# Mode navigation privée — les extensions faussent le score
# 2. WebPageTest (appareils réels)
# https://webpagetest.org
# Régions et vitesses réseau configurables
# 3. Panneau Performance Chrome DevTools
# Enregistrer le chargement, analyser les goulots
# Durée de chaque tâche visible
# 4. Analyse des paquets npm
npx webpack-bundle-analyzer
# Visualise le plus gros bundle
# 5. Analyse d'images
npx sharp-cli info image.jpg
# Dimensions, format et taille du fichier

Pièges à éviter

  1. ❌ Ne pas tester Lighthouse en production (navigation privée ou extensions désactivées)
  2. ❌ Ne pas tester une seule fois (au moins 3 runs, moyenne — le réseau fluctue)
  3. ❌ Ne pas tester uniquement sur desktop (mobile plus important — 75 % du trafic)
  4. ❌ Ne pas ignorer le Network throttling (simuler un réseau lent, ex. Fast 3G)
  5. ❌ Ne jamais lazy-loader les images LCP (piège que j’ai vécu)

Conclusion

L’optimisation des performances n’est pas un one-shot, c’est un processus continu — comme le sport, ça demande de la régularité. Voir Lighthouse passer de 60 à 90, c’est une vraie satisfaction.
Ce n’est pas que de la technique : c’est du respect envers l’utilisateur. Chaque seconde gagnée retient plus de visiteurs. Sur mobile, la patience tient souvent 3 secondes — et vous pouvez réduire ces 3 secondes à 1.
Ensemble, offrons à chaque visiteur une navigation fluide.

FAQ

Quels sont les seuils des trois Core Web Vitals ?
LCP (Largest Contentful Paint) :
• Moins de 2,5 s : bon, 2,5–4 s : à améliorer, plus de 4 s : mauvais

INP (Interaction to Next Paint) :
• Moins de 200 ms : bon, 200–500 ms : à améliorer, plus de 500 ms : mauvais
• Depuis mars 2024, l'INP remplace le FID comme métrique centrale

CLS (Cumulative Layout Shift) :
• Moins de 0,1 : bon, 0,1–0,25 : à améliorer, plus de 0,25 : mauvais

Données clés :
• Temps de chargement de 3 à 5 s : taux de rebond +38 %
• Mobile > 3 s : 53 % des utilisateurs partent
• LCP optimisé : conversion +7 à 15 %
Comment optimiser le LCP (Largest Contentful Paint) ?
Les images causent plus de 70 % des problèmes LCP :

1) Formats modernes (AVIF +41 % vs JPEG, WebP +30 %)
• Cas réel : hero de 500 Ko à 120 Ko, LCP de 4,2 s à 2,1 s

2) Images LCP avec priority, pas de lazy loading — eager above-the-fold

3) srcset responsif économise jusqu'à 60 % de bande passante

4) CDN et preload des images critiques

5) Temps de réponse serveur (CDN, SSR/SSG, requêtes BDD)

Priorité : optimisation images = ROI maximal, effort faible, effet rapide.
Comment optimiser l'INP (Interaction to Next Paint) ?
Optimisations clés :

1) Code splitting par route (React lazy, Vue defineAsyncComponent)
• Cas réel : 1,2 Mo → 200 Ko au first view, INP 450 ms → 180 ms

2) Scripts tiers différés (defer ou délai 3 s), pattern Facade pour composants lourds

3) Supprimer les embeds auto (ex. YouTube : INP 650 ms → 220 ms)

4) Découper les longues tâches avec requestIdleCallback

5) Web Workers pour calculs complexes

6) Debounce, throttle et écouteurs passifs
Comment optimiser le CLS (Cumulative Layout Shift) ?
Le CLS est le plus facile à corriger :

1) Dimensions sur les images (width/height ou CSS aspect-ratio)
• Cas réel : CLS 0,35 → 0,05

2) Polices (font-display: swap, preload, polices système ou Variable Font)

3) Espace réservé (min-height pub, skeleton screens)

4) Ne pas insérer au-dessus du contenu existant (position fixed)

5) Animer avec transform plutôt que top/left (composite, pas layout)
Quelles sont les priorités d'optimisation des performances ?
P0 (ROI très élevé, faible effort) :
• Dimensions des images
• Conversion de format (AVIF/WebP)
• Images LCP (priority, pas de lazy loading)
• Scripts tiers différés

P1 (ROI élevé) :
• Polices (font-display: swap)
• Code splitting
• CDN

P2 (ROI moyen, effort élevé) :
• SSR/SSG

P3 (ROI faible, effort élevé) :
• Web Workers

Conseil : commencer par P0 — seules les dimensions images rapportent facilement 10 points. Ne pas attaquer le SSR avant les images.
Peut-on lazy-loader les images LCP ?
Absolument pas — c'est le piège le plus courant.

Les images LCP (hero above-the-fold) doivent avoir priority ou loading="eager", pas de lazy loading. Le lazy loading sert aux images sous la ligne de flottaison.

Erreur :
<img src="hero.jpg" loading="lazy"> — le score LCP baisse

Correct :
• eager above-the-fold, lazy en dessous
• Aucune image visible au premier écran ne doit être lazy-loadée
Comment mesurer et tester les Core Web Vitals ?
Outils :

1) Lighthouse (Chrome DevTools, référence — navigation privée contre les extensions)

2) WebPageTest (appareils réels, régions et vitesses réseau)

3) Panneau Performance Chrome DevTools (enregistrer le chargement, trouver les goulots)

4) webpack-bundle-analyzer (visualiser les plus gros bundles)

5) sharp-cli info (dimensions, format, taille des images)

Pièges :
• Ne pas tester en production
• Ne pas mesurer une seule fois (≥ 3 runs, moyenne)
• Ne pas tester desktop seul (mobile : 75 % du trafic)
• Ne pas ignorer le Network throttling (réseau lent simulé)

14 min de lecture · Publié le: 24 nov. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog