Retour au cours

frontend / html

Performance : lazy loading et resource hints niveau expert

Leçon 141 exercice

Explication

Ce que vous allez apprendre

  • Prioriser une ressource critique avec rel="preload"
  • Établir une connexion réseau en avance avec rel="preconnect"
  • Distinguer précisément async et defer sur une balise <script>
  • Appliquer loading="lazy" uniquement là où c'est pertinent, jamais sur l'image LCP
  • Éliminer le flash de texte invisible (FOIT) avec font-display: swap

Dans quel contexte ?

Le score Lighthouse Performance de accueil.html stagne à 61/100 à cause d'un LCP (Largest Contentful Paint) trop lent : l'image bannière en haut de page se charge après plusieurs polices et scripts non critiques. En ajoutant <link rel="preload" href="/img/hero.webp" as="image" fetchpriority="high"> dans le <head>, le navigateur télécharge cette image en priorité maximale, avant même de terminer le parsing du reste de la page.

Étape 1 : comment le navigateur charge par défaut

Par défaut, un navigateur télécharge les ressources d'une page dans l'ordre où il les découvre en lisant le HTML. Simple, mais pas toujours optimal.

Étape 2 : le problème concret que ça pose

Une police de caractères critique pour l'affichage du texte, découverte tardivement dans une feuille de style, arrive après des scripts moins urgents. Cette leçon apprend à reprendre la main sur ces priorités.

Étape 3 : le niveau d'urgence maximal, preload

D'abord, le plus fort : preload dit "télécharge ça en priorité maximale, j'en ai besoin très vite". On l'utilise pour une ressource vraiment critique de la page actuelle.

Étape 4 : un niveau plus léger, preconnect

Vient ensuite preconnect, qui dit "établis déjà la connexion réseau vers ce serveur, même si je ne sais pas encore exactement quoi lui demander" — utile pour une police hébergée sur un domaine tiers.

Étape 5 : anticiper le futur, prefetch

Enfin, prefetch anticipe un besoin FUTUR probable, pas immédiat, comme la page suivante que l'utilisateur visitera sans doute.

Étape 6 : la confusion classique entre async et defer

Passons maintenant aux scripts, où une confusion revient sans cesse. defer télécharge le script en parallèle du HTML, mais ne l'exécute qu'une fois le HTML entièrement parsé, dans l'ordre d'apparition — le bon choix pour du code applicatif qui manipule le DOM.

async, lui, télécharge aussi en parallèle, mais exécute le script DÈS qu'il est prêt, sans garantie d'ordre — adapté à du code indépendant comme un script d'analytics.

Attribut <script>TéléchargementExécutionCas d'usage
(aucun)Bloque le parsing HTMLImmédiateÀ éviter sauf nécessité
deferParallèleAprès le parsing, dans l'ordreCode applicatif dépendant du DOM
asyncParallèleDès que prêt, ordre non garantiAnalytics, publicité, code indépendant

Prérequis

Cette leçon suppose une compréhension de base du chargement d'une page (voir la leçon sur la structure HTML) ; elle vise un niveau intermédiaire à expert en optimisation.

Le piège du lazy loading mal placé

Il reste un dernier piège fréquent : appliquer loading="lazy" à TOUTES les images, y compris la plus visible en haut de page (souvent l'élément "LCP" mesuré par Google). C'est contre-productif : cela retarde justement l'image la plus importante pour la perception de vitesse.

Le lazy loading doit cibler uniquement ce qui est hors de l'écran initial.

Piège fréquent

Appliquer loading="lazy" sur l'image bannière visible dès l'ouverture de la page retarde son chargement au lieu de l'accélérer, et pénalise directement le score LCP. Réservez loading="lazy" aux images situées sous la ligne de flottaison (le "pli").

Et ensuite ?

Une fois la performance de chargement maîtrisée, la prochaine leçon aborde une interaction bien différente : le drag & drop natif.

Commandes & code

Performance : lazy loading et resource hints

Réduire le temps avant contenu utile en indiquant au navigateur ce qu'il doit prioriser.

html
<!-- preload : télécharge en priorité une ressource critique découverte tard -->
<head>
    <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
    <link rel="preload" href="/css/critique.css" as="style">
    <link rel="preload" href="/img/hero.webp" as="image" fetchpriority="high">

    <!-- preconnect : établit la connexion (DNS + TCP + TLS) en avance -->
    <link rel="preconnect" href="https://api.technologik.dev">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

    <!-- dns-prefetch : repli léger pour les domaines tiers secondaires -->
    <link rel="dns-prefetch" href="https://cdn.exemple.com">

    <!-- prefetch : anticipe une ressource probablement nécessaire bientôt
         (page suivante, pas la page courante) -->
    <link rel="prefetch" href="/cours/css" as="document">

    <!-- modulepreload : équivalent preload pour les modules JS -->
    <link rel="modulepreload" href="/js/app.mjs">
</head>
html
<!-- Chargement de script : impact direct sur le parsing HTML -->
<script src="/js/analytics.js" async></script>
<!-- async : téléchargé en parallèle, exécuté DÈS qu'il est prêt (ordre non garanti)
     -> pour du code indépendant (analytics, ads) -->

<script src="/js/app.js" defer></script>
<!-- defer : téléchargé en parallèle, exécuté APRÈS le parsing HTML, dans l'ordre
     -> pour le code applicatif qui dépend du DOM -->

<script src="/js/legacy.js"></script>
<!-- ni async ni defer : bloque le parsing HTML jusqu'à exécution complète
     -> à éviter sauf nécessité absolue -->
html
<!-- Lazy loading natif : images et iframes hors du viewport initial -->
<img src="/img/photo-1.jpg" alt="Photo 1" loading="lazy">
<img src="/img/photo-2.jpg" alt="Photo 2" loading="lazy">

<!-- MAIS PAS sur l'image la plus visible (souvent le LCP - Largest Contentful Paint) -->
<img src="/img/hero.webp" alt="Bannière principale" fetchpriority="high">
<!-- loading="eager" est la valeur par défaut : pas besoin de le préciser,
     fetchpriority="high" suffit à signaler l'importance -->

<iframe src="https://youtube.com/embed/xyz" loading="lazy" title="Vidéo de présentation"></iframe>
html
<!-- Fonts : éviter le FOIT (flash of invisible text) -->
<style>
@font-face {
    font-family: "Inter";
    src: url("/fonts/inter-var.woff2") format("woff2");
    font-display: swap; /* affiche le texte en fallback pendant le chargement */
    font-weight: 100 900; /* variable font : une seule ressource pour toutes les graisses */
}
</style>

<!-- Critical CSS inline + reste différé -->
<style>
    /* CSS critique above-the-fold, injecté directement */
    body { margin: 0; font-family: system-ui; }
    header { min-height: 64px; }
</style>
<link rel="preload" href="/css/reste.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/reste.css"></noscript>
Resource hintQuand l'utiliser
preconnectdomaine tiers utilisé très tôt (API, fonts)
preloadressource critique de la page actuelle
prefetchressource probable pour la navigation suivante
modulepreloadmodules ES critiques

Résumé

  • defer pour le JS applicatif, async pour le JS indépendant, jamais les deux ensemble.
  • fetchpriority="high" sur l'image LCP, loading="lazy" sur tout le reste sous le pli.
  • preconnect économise une latence complète de handshake sur les domaines tiers critiques.
  • font-display: swap élimine le texte invisible pendant le chargement des polices.

Exercices pratiques

1 disponible
1

Mission : sauver le score Lighthouse de accueil.html

Objectif : Corriger un LCP trop lent en priorisant l'image bannière, sans commettre l'erreur inverse du lazy loading mal placé.

Contexte

Le score Lighthouse Performance de accueil.html stagne à 61/100 à cause d'un LCP (Largest Contentful Paint) trop lent : l'image bannière hero.webp en haut de page se charge après plusieurs polices et scripts non critiques. Un développeur, pensant bien faire, a même ajouté loading="lazy" sur cette image bannière pour "l'optimiser".

Ta mission : corriger la priorité de chargement de cette image.

Résoudre l’exercice →