Cours Next.js · Module 3: Styles et optimisation visuelle

Stratégies de chargement des images (lazy, eager, priority) dans Next.js 16

17 min de lecture
Dans cette leçon13

À Metropolis Quantum, le système de transport de la ville est organisé selon différentes priorités. Les véhicules d'urgence ont la priorité la plus élevée et ont toujours la voie libre. Les transports en commun ont une priorité moyenne, et les véhicules privés la plus basse. Cette gestion intelligente du trafic garantit que les ressources les plus importantes atteignent toujours leur destination le plus vite, tandis que les moins critiques peuvent attendre.

De même, dans les applications web, les différentes images ont différents niveaux d'importance. L'image hero de la page devrait se charger immédiatement, tandis que les images placées plus bas peuvent n'être chargées que lorsque l'utilisateur les fait défiler. Next.js 16 offre différentes stratégies de chargement des images qui te permettent de personnaliser leur chargement en fonction de leur importance et de leur position sur la page.

Trois grandes stratégies de chargement des images

Dans Next.js 16, nous disposons de trois grandes stratégies de chargement des images :

  1. Lazy - la stratégie par défaut, les images ne sont chargées que lorsqu'elles approchent de la zone visible (viewport)
  2. Eager - les images sont chargées immédiatement, quelle que soit leur position sur la page
  3. Priority - les images sont chargées avec la priorité la plus élevée, idéal pour les images « above the fold ». Dans Next.js 16, tu actives cette stratégie avec l'attribut preload, qui a remplacé l'attribut priority, désormais déprécié

Chacune de ces stratégies a son cas d'usage et son impact sur la performance de la page.

Lazy loading

Le lazy loading est la stratégie par défaut du composant Image dans Next.js 16. Il fonctionne selon un principe simple :

Ne charge que ce que l'utilisateur voit actuellement ou verra bientôt.

Comment fonctionne le lazy loading ?

  1. Next.js retarde automatiquement le chargement des images situées hors de la zone visible (viewport)
  2. Les images ne sont chargées que lorsque l'utilisateur fait défiler la page et qu'elles approchent de la zone visible
  3. En coulisses, on utilise la fonctionnalité moderne du navigateur loading="lazy", avec des optimisations supplémentaires

Exemple d'utilisation du lazy loading :

1import Image from 'next/image';
2
3export default function QuantumGallery() {
4  return (
5    <div className="gallery">
6      {Array.from({ length: 20 }).map((_, index) => (
7        <div key={index} className="relative h-64 w-full">
8          <Image
9            src={`/images/quantum-city-${index + 1}.jpg`}
10            alt={`Metropolis Quantum - photo ${index + 1}`}
11            fill
12            style={{ objectFit: 'cover' }}
13            // No need to add loading="lazy" - it is the default value
14          />
15        </div>
16      ))}
17    </div>
18  );
19}

Avantages du lazy loading :

  1. Un chargement initial de page plus rapide - seules les images critiques sont chargées immédiatement
  2. Une moindre consommation de données - les images que l'utilisateur n'atteint jamais ne sont pas chargées
  3. Une meilleure performance - le navigateur peut concentrer ses ressources sur les éléments actuellement importants

Scénarios où le lazy loading vaut la peine :

  • Les pages longues avec beaucoup d'images
  • Les galeries de photos
  • Les listes de produits
  • Les carrousels d'images
  • Les pages à défilement infini

Eager loading

La stratégie eager loading est l'opposée du lazy loading. Au lieu d'attendre que les images approchent de la zone visible, elles sont chargées immédiatement pendant le chargement de la page.

Comment activer l'eager loading :

1import Image from 'next/image';
2
3export default function QuantumLogo() {
4  return (
5    <Image
6      src="/images/quantum-logo.png"
7      alt="Logo Metropolis Quantum"
8      width={200}
9      height={100}
10      loading="eager"
11    />
12  );
13}

Avantages de l'eager loading :

  1. Une disponibilité immédiate - toutes les images sont disponibles dès le chargement de la page
  2. Aucun délai - l'utilisateur n'a pas à attendre le chargement des images pendant le défilement
  3. Un comportement plus prévisible - toutes les ressources sont chargées en un seul cycle

Scénarios où l'eager loading vaut la peine :

  • Les pages courtes avec peu d'images
  • Les pages où toutes les images sont importantes pour le contexte
  • Les applications où un défilement fluide sans délai est crucial
  • Les applications offline-first qui veulent charger toutes les ressources d'un coup

Priority loading

Le priority loading est une stratégie spéciale pour les images les plus importantes de la page, en particulier celles visibles « above the fold » (dans la partie supérieure de la page, visibles sans défilement).

Comment activer le priority loading :

1import Image from 'next/image';
2
3export default function QuantumHero() {
4  return (
5    <Image
6      src="/images/quantum-hero.jpg"
7      alt="Bienvenue à Metropolis Quantum"
8      width={1920}
9      height={1080}
10      preload
11    />
12  );
13}

Quand tu utilises l'attribut preload (appelé priority dans Next.js 15 et les versions précédentes) :

  1. Next.js ajoute dans le <head> une balise <link rel="preload"> pour cette image
  2. Le navigateur commence tout de suite à télécharger l'image, avant de l'atteindre dans le contenu de la page
  3. Le plus grand élément de la page (LCP) apparaît plus tôt

L'ancien attribut priority fonctionne encore dans Next.js 16, mais il est déprécié. La documentation conseille aussi de n'utiliser preload que lorsque l'image est à coup sûr l'élément LCP ; dans les autres cas, choisis loading="eager" ou fetchPriority="high".

Avantages du priority loading :

  1. Un LCP (Largest Contentful Paint) plus faible - une métrique clé des Core Web Vitals
  2. Un affichage plus rapide des images les plus importantes - une meilleure performance perçue
  3. L'optimisation des Core Web Vitals - un impact positif sur le SEO

Scénarios où priority vaut la peine :

  • Les images hero de la page principale
  • Le logo de la marque
  • Les images de produit sur les pages de détail
  • Les avatars des utilisateurs dans les applications sociales
  • Toute image qui constitue le contenu principal de la page

Remarques clés sur l'attribut preload :

  • Utilise preload avec parcimonie - uniquement pour les images les plus importantes
  • La documentation de Next.js recommande preload uniquement pour l'image LCP (Largest Contentful Paint), et non pour plusieurs images susceptibles de le devenir selon la largeur de l'écran
  • Trop d'images avec preload peut paradoxalement réduire la performance

Comparaison des stratégies de chargement

StratégieAttributCas d'usageImpact sur la performance
Lazyloading="lazy" (par défaut)Images sous la ligne de flottaison, galeries, listesChargement initial plus rapide, économie de données
Eagerloading="eager"Petites pages, toutes les images importantesChargement initial plus lent, meilleure réactivité au défilement
Prioritypreload (anciennement priority)Images « above the fold », images hero, contenu principalMeilleur LCP, affichage plus rapide des images clés

Stratégies avancées de chargement des images

Au-delà des stratégies de base, Next.js 16 permet des approches plus avancées du chargement des images :

1. Précharger les images importantes avec useRouter

Parfois, nous voulons précharger les images d'une page vers laquelle l'utilisateur va probablement naviguer :

1'use client';
2
3import { useEffect } from 'react';
4import { useRouter } from 'next/navigation';
5import Image from 'next/image';
6
7export default function ProductCard({ product, isPriority }) {
8  const router = useRouter();
9
10  useEffect(() => {
11    // Preload product detail image on mouse hover
12    const preloadDetailImage = () => {
13      router.prefetch(`/products/${product.id}`);
14    };
15
16    const cardElement = document.getElementById(`product-card-${product.id}`);
17    if (cardElement) {
18      cardElement.addEventListener('mouseenter', preloadDetailImage);
19      return () => {
20        cardElement.removeEventListener('mouseenter', preloadDetailImage);
21      };
22    }
23  }, [product.id, router]);
24
25  return (
26    <div
27      id={`product-card-${product.id}`}
28      className="relative h-64 overflow-hidden rounded-lg"
29      onClick={() => router.push(`/products/${product.id}`)}
30    >
31      <Image
32        src={product.thumbnail}
33        alt={product.title}
34        fill
35        style={{ objectFit: 'cover' }}
36        preload={isPriority}
37      />
38      <div className="absolute bottom-0 w-full bg-black/60 p-4">
39        <h3 className="text-white font-bold">{product.title}</h3>
40      </div>
41    </div>
42  );
43}

2. Bascule dynamique de la priorité selon la visibilité

Nous pouvons attribuer dynamiquement une priorité aux images actuellement visibles :

1'use client';
2
3import { useState, useEffect, useRef } from 'react';
4import Image from 'next/image';
5
6export default function DynamicPriorityImage({ src, alt }) {
7  const [isPriority, setIsPriority] = useState(false);
8  const imageRef = useRef(null);
9
10  useEffect(() => {
11    const observer = new IntersectionObserver(
12      ([entry]) => {
13        if (entry.isIntersecting) {
14          setIsPriority(true);
15          observer.disconnect();
16        }
17      },
18      { threshold: 0.1 }
19    );
20
21    if (imageRef.current) {
22      observer.observe(imageRef.current);
23    }
24
25    return () => {
26      observer.disconnect();
27    };
28  }, []);
29
30  return (
31    <div ref={imageRef} className="relative h-64 w-full">
32      <Image
33        src={src}
34        alt={alt}
35        fill
36        style={{ objectFit: 'cover' }}
37        preload={isPriority}
38      />
39    </div>
40  );
41}

3. Chargement progressif des images

Tu peux combiner les stratégies pour d'abord afficher une image de basse qualité, puis la remplacer par une version de haute qualité :

1'use client';
2
3import { useState } from 'react';
4import Image from 'next/image';
5
6export default function ProgressiveImage() {
7  const [isLoaded, setIsLoaded] = useState(false);
8
9  return (
10    <div className="relative h-96 w-full">
11      {/* Niskokwalitywowy placeholder */}
12      <Image
13        src="/images/quantum-city-low.jpg"
14        alt="Metropolis Quantum"
15        fill
16        style={{
17          objectFit: 'cover',
18          opacity: isLoaded ? 0 : 1,
19          transition: 'opacity 0.5s ease-in-out'
20        }}
21        preload // Placeholder loaded immediately
22      />
23
24      {/* High quality image */}
25      <Image
26        src="/images/quantum-city-high.jpg"
27        alt="Metropolis Quantum"
28        fill
29        style={{
30          objectFit: 'cover',
31          opacity: isLoaded ? 1 : 0,
32          transition: 'opacity 0.5s ease-in-out'
33        }}
34        onLoad={() => setIsLoaded(true)}
35      />
36    </div>
37  );
38}

Optimisation des images pour différents appareils

Les stratégies de chargement peuvent être combinées à des techniques d'adaptation des images aux différents appareils :

1. Images responsives avec différentes sources

1import Image from 'next/image';
2
3export default function ResponsiveHero() {
4  return (
5    <>
6      {/* Image for mobile devices */}
7      <div className="md:hidden">
8        <Image
9          src="/images/hero-mobile.jpg"
10          alt="Metropolis Quantum"
11          width={640}
12          height={960}
13          preload // Priority for the main image
14        />
15      </div>
16
17      {/* Image for tablets */}
18      <div className="hidden md:block lg:hidden">
19        <Image
20          src="/images/hero-tablet.jpg"
21          alt="Metropolis Quantum"
22          width={1024}
23          height={768}
24          preload
25        />
26      </div>
27
28      {/* Image for desktops */}
29      <div className="hidden lg:block">
30        <Image
31          src="/images/hero-desktop.jpg"
32          alt="Metropolis Quantum"
33          width={1920}
34          height={1080}
35          preload
36        />
37      </div>
38    </>
39  );
40}

2. Chargement adaptatif selon la connexion

1'use client';
2
3import { useState, useEffect } from 'react';
4import Image from 'next/image';
5
6export default function AdaptiveLoadingImage() {
7  const [connectionType, setConnectionType] = useState('high');
8
9  useEffect(() => {
10    // Detect connection quality
11    const connection = navigator.connection ||
12                      navigator.mozConnection ||
13                      navigator.webkitConnection;
14
15    if (connection) {
16      if (connection.effectiveType === '4g' && !connection.saveData) {
17        setConnectionType('high');
18      } else {
19        setConnectionType('low');
20      }
21
22      // Listen for changes in connection quality
23      const updateConnectionStatus = () => {
24        if (connection.effectiveType === '4g' && !connection.saveData) {
25          setConnectionType('high');
26        } else {
27          setConnectionType('low');
28        }
29      };
30
31      connection.addEventListener('change', updateConnectionStatus);
32      return () => connection.removeEventListener('change', updateConnectionStatus);
33    }
34  }, []);
35
36  return (
37    <div className="relative h-64 w-full">
38      <Image
39        src={connectionType === 'high' ? '/images/high-quality.jpg' : '/images/low-quality.jpg'}
40        alt="Image adaptative"
41        fill
42        style={{ objectFit: 'cover' }}
43        loading={connectionType === 'high' ? 'eager' : 'lazy'}
44        quality={connectionType === 'high' ? 90 : 50}
45      />
46    </div>
47  );
48}

N'oublie pas que dans Next.js 16, les deux valeurs de quality (90 et 50) doivent figurer dans la liste images.qualities de next.config. Par défaut, cette liste ne contient que 75 : sans l'étendre, les deux variantes recevront la qualité 75.

Implémenter des stratégies de chargement avancées dans une application réelle

Exemple : page produit avec différentes stratégies de chargement

1// app/products/[id]/page.tsx
2import Image from 'next/image';
3import { getProduct, getRelatedProducts } from '@/lib/api';
4import RelatedProducts from '@/components/RelatedProducts';
5
6export default async function ProductPage({ params }) {
7  const product = await getProduct((await params).id);
8  const relatedProducts = await getRelatedProducts((await params).id);
9
10  return (
11    <div className="container mx-auto px-4 py-8">
12      <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
13        {/* Main product image - priority loading */}
14        <div className="relative aspect-square">
15          <Image
16            src={product.mainImage}
17            alt={product.name}
18            fill
19            style={{ objectFit: 'contain' }}
20            preload
21            sizes="(max-width: 768px) 100vw, 50vw"
22          />
23        </div>
24
25        <div>
26          <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
27          <p className="text-xl mb-4">{product.price}</p>
28          <div className="prose max-w-none">
29            <p>{product.description}</p>
30          </div>
31
32          <button className="mt-6 bg-blue-600 text-white px-6 py-3 rounded-md">
33            Ajouter au panier
34          </button>
35        </div>
36      </div>
37
38      {/* Additional photos gallery - lazy loading (default) */}
39      <div className="mt-12">
40        <h2 className="text-2xl font-bold mb-6">Galerie du produit</h2>
41        <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
42          {product.gallery.map((image, index) => (
43            <div key={index} className="relative aspect-square">
44              <Image
45                src={image.url}
46                alt={`${product.name} - photo ${index + 1}`}
47                fill
48                style={{ objectFit: 'cover' }}
49                sizes="(max-width: 768px) 50vw, 25vw"
50                // Default lazy loading
51              />
52            </div>
53          ))}
54        </div>
55      </div>
56
57      {/* Produits similaires - eager loading pour le premier élément */}
58      <div className="mt-12">
59        <h2 className="text-2xl font-bold mb-6">Podobne produkty</h2>
60        <RelatedProducts products={relatedProducts} />
61      </div>
62    </div>
63  );
64}
1// components/RelatedProducts.tsx
2'use client';
3
4import Image from 'next/image';
5import Link from 'next/link';
6import { useRef, useEffect } from 'react';
7
8export default function RelatedProducts({ products }) {
9  const containerRef = useRef(null);
10
11  useEffect(() => {
12    // Preload images when hovering over the section
13    const preloadImages = () => {
14      products.forEach(product => {
15        const img = new Image();
16        img.src = product.image;
17      });
18    };
19
20    const container = containerRef.current;
21    if (container) {
22      container.addEventListener('mouseenter', preloadImages, { once: true });
23      return () => {
24        container.removeEventListener('mouseenter', preloadImages);
25      };
26    }
27  }, [products]);
28
29  return (
30    <div ref={containerRef} className="grid grid-cols-2 md:grid-cols-4 gap-4">
31      {products.map((product, index) => (
32        <Link key={product.id} href={`/products/${product.id}`}>
33          <div className="relative aspect-square rounded-lg overflow-hidden">
34            <Image
35              src={product.image}
36              alt={product.name}
37              fill
38              style={{ objectFit: 'cover' }}
39              // Eager loading uniquement pour le premier produit
40              loading={index === 0 ? 'eager' : 'lazy'}
41              sizes="(max-width: 768px) 50vw, 25vw"
42            />
43            <div className="absolute bottom-0 w-full bg-black/60 p-2">
44              <p className="text-white text-sm font-medium truncate">
45                {product.name}
46              </p>
47            </div>
48          </div>
49        </Link>
50      ))}
51    </div>
52  );
53}

Impact des stratégies de chargement sur les Core Web Vitals

Les Core Web Vitals sont un ensemble de métriques Google qui mesurent l'expérience utilisateur sur les sites web. Le choix de la bonne stratégie de chargement des images a un impact direct sur ces métriques :

1. LCP (Largest Contentful Paint)

Le LCP mesure le temps de chargement du plus grand élément visible dans le viewport, qui est souvent une image.

Impact des stratégies de chargement :

  • Le priority loading de l'image principale peut considérablement améliorer le LCP
  • L'eager loading de toutes les images peut dégrader le LCP en dispersant les ressources
  • Le lazy loading des images sous la ligne de flottaison n'affecte pas le LCP mais améliore la performance globale

2. CLS (Cumulative Layout Shift)

Le CLS mesure l'instabilité de la mise en page pendant le chargement.

Impact des stratégies de chargement :

  • Toutes les stratégies peuvent minimiser le CLS si les images ont des dimensions spécifiées
  • Le priority loading avec placeholder="blur" offre le meilleur CLS car il réserve l'espace immédiatement
  • Spécifie toujours width et height ou utilise fill avec un conteneur aux dimensions définies

3. FID (First Input Delay)

Le FID mesure le temps entre la première interaction de l'utilisateur et le moment où le navigateur peut y répondre.

Impact des stratégies de chargement :

  • Le lazy loading peut améliorer le FID en réduisant le travail du navigateur pendant le chargement
  • L'eager loading peut dégrader le FID en augmentant la charge du thread principal
  • Le priority loading des images clés peut être un compromis

Mesurer et optimiser les stratégies de chargement

Pour t'assurer que les stratégies de chargement choisies fonctionnent de manière optimale, il vaut la peine de mesurer leur impact :

1. Outils de mesure de la performance

  • Lighthouse - un outil d'analyse de performance complet
  • Chrome DevTools Performance - une analyse détaillée du chargement des ressources
  • WebPageTest - des tests de performance dans diverses conditions
  • Core Web Vitals dans la Google Search Console

2. Tests A/B des stratégies de chargement

1'use client';
2
3import { useState, useEffect } from 'react';
4import Image from 'next/image';
5
6export default function ABTestImage({ src, alt }) {
7  const [testGroup, setTestGroup] = useState('');
8
9  useEffect(() => {
10    // Assign user to group A or B
11    const group = Math.random() > 0.5 ? 'A' : 'B';
12    setTestGroup(group);
13
14    // Save données télémétriques
15    const trackImageLoad = (time) => {
16      // Sending data to the analytics system
17      console.log(`Image loaded in ${time}ms (Group ${group})`);
18    };
19
20    const startTime = performance.now();
21    const onLoadComplete = () => {
22      const loadTime = performance.now() - startTime;
23      trackImageLoad(loadTime);
24    };
25
26    // Listen for the image load event
27    const imageEl = document.getElementById('test-image');
28    if (imageEl) {
29      imageEl.addEventListener('load', onLoadComplete);
30      return () => {
31        imageEl.removeEventListener('load', onLoadComplete);
32      };
33    }
34  }, []);
35
36  if (!testGroup) return null;
37
38  return (
39    <div className="relative h-96 w-full">
40      <Image
41        id="test-image"
42        src={src}
43        alt={alt}
44        fill
45        style={{ objectFit: 'cover' }}
46        preload={testGroup === 'A'} // Group A uses preload, group B does not
47      />
48    </div>
49  );
50}

Bonnes pratiques pour les stratégies de chargement des images

  1. N'utilise preload que pour les images above-the-fold - idéalement pour une seule image LCP par page

  2. Utilise le lazy loading par défaut - cela économise des ressources et accélère le chargement initial de la page

  3. Surveille les métriques Web Vitals - vérifie régulièrement comment les stratégies de chargement affectent la performance

  4. Adapte les stratégies au type de page :

    • Pages produit : preload pour la photo principale du produit
    • Pages de catégorie : lazy pour la plupart des images, eager pour les premières
    • Page d'accueil : preload pour l'image hero, lazy pour le reste
  5. Prends en compte les appareils des utilisateurs :

    • Les utilisateurs mobiles ont souvent des connexions plus lentes - il est d'autant plus important d'utiliser lazy et des images plus petites
    • Les utilisateurs desktop peuvent bénéficier de eager pour une meilleure expérience de défilement
  6. Utilise des placeholders pour une meilleure expérience de chargement :

    • Pour les images avec preload - affiche un placeholder blur ou empty
    • Pour les images avec lazy - utilise blur pour une transition fluide

Scénarios pour différentes stratégies

1. Page d'accueil e-commerce

1export default function Homepage() {
2  return (
3    <div>
4      {/* Hero section - priority */}
5      <div className="relative h-[80vh]">
6        <Image
7          src="/images/hero.jpg"
8          alt="Bienvenue sur Quantum Shop"
9          fill
10          style={{ objectFit: 'cover' }}
11          preload
12        />
13      </div>
14
15      {/* Promotions - eager for the first two */}
16      <div className="grid grid-cols-2 md:grid-cols-4 gap-4 my-8">
17        {promotions.map((promo, index) => (
18          <div key={promo.id} className="relative aspect-square">
19            <Image
20              src={promo.image}
21              alt={promo.title}
22              fill
23              style={{ objectFit: 'cover' }}
24              loading={index < 2 ? 'eager' : 'lazy'}
25            />
26          </div>
27        ))}
28      </div>
29
30      {/* Product categories - lazy (default) */}
31      <div className="grid grid-cols-2 md:grid-cols-3 gap-6 my-12">
32        {categories.map(category => (
33          <div key={category.id} className="relative aspect-video">
34            <Image
35              src={category.image}
36              alt={category.name}
37              fill
38              style={{ objectFit: 'cover' }}
39              // default lazy loading
40            />
41          </div>
42        ))}
43      </div>
44    </div>
45  );
46}

2. Blog avec de longs articles

1export default function BlogPost({ post }) {
2  return (
3    <article className="max-w-3xl mx-auto py-8">
4      {/* Main article image - priority */}
5      <div className="relative aspect-video mb-8">
6        <Image
7          src={post.featuredImage}
8          alt={post.title}
9          fill
10          style={{ objectFit: 'cover' }}
11          preload
12        />
13      </div>
14
15      <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
16      <div className="prose max-w-none">
17        {post.content.map((block, index) => {
18          if (block.type === 'paragraph') {
19            return <p key={index}>{block.content}</p>;
20          }
21          if (block.type === 'image') {
22            return (
23              <div key={index} className="relative aspect-video my-8">
24                <Image
25                  src={block.url}
26                  alt={block.caption || ''}
27                  fill
28                  style={{ objectFit: 'contain' }}
29                  // default lazy loading for images in content
30                />
31                {block.caption && (
32                  <p className="text-sm text-gray-500 mt-2 text-center">
33                    {block.caption}
34                  </p>
35                )}
36              </div>
37            );
38          }
39          return null;
40        })}
41      </div>
42
43      {/* Related articles - all lazy */}
44      <div className="mt-12">
45        <h2 className="text-2xl font-bold mb-6">Related Articles</h2>
46        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
47          {post.relatedPosts.map(relatedPost => (
48            <div key={relatedPost.id} className="relative aspect-video">
49              <Image
50                src={relatedPost.thumbnail}
51                alt={relatedPost.title}
52                fill
53                style={{ objectFit: 'cover' }}
54                // All lazy (default)
55              />
56            </div>
57          ))}
58        </div>
59      </div>
60    </article>
61  );
62}

Résumé

Le choix de la bonne stratégie de chargement des images est crucial pour optimiser la performance et l'expérience utilisateur. Tout comme dans le système de transport de Metropolis Quantum, où les véhicules de priorités différentes se déplacent avec une urgence variable, dans une application web les images peuvent être chargées avec différentes stratégies selon leur importance :

  1. Le priority loading - pour les images « above the fold » les plus importantes, qui ont un impact direct sur le LCP et la première impression de l'utilisateur

  2. L'eager loading - pour les images moins critiques mais tout de même importantes, qui seront probablement nécessaires peu après le chargement de la page

  3. Le lazy loading - pour la plupart des images, en particulier celles sous la ligne de flottaison, qui ne seront chargées que lorsque l'utilisateur les atteindra

Lorsque tu choisis une stratégie de chargement pour tes images, prends en compte :

  • La visibilité de l'image sans défilement
  • L'impact sur les métriques de performance clés
  • Le type de contenu et son importance
  • Les appareils et connexions internet de tes utilisateurs

En appliquant correctement les stratégies de chargement des images, tu peux créer une application qui se charge vite, gère efficacement ses ressources et offre une expérience utilisateur fluide - tout comme le système de transport intelligent de Metropolis Quantum, qui assure une circulation fluide des véhicules de priorités différentes.

Dans le prochain chapitre, nous nous concentrerons sur l'optimisation des polices et le composant Font, ce qui nous permettra d'améliorer encore la performance et l'esthétique de notre application Next.js 16.

Code de cette leçon : app/page.tsx
1// Image Loading Strategies - lazy, eager, priority
2'use client';
3
4import { useState } from 'react';
5
6export default function LoadingStrategiesDemo() {
7  const [activeStrategy, setActiveStrategy] = useState('lazy');
8
9  const strategies = [
10    {
11      id: 'lazy',
12      name: 'Lazy Loading',
13      icon: '',
14      color: 'blue',
15      description: 'Default strategy. Images load only when approaching the viewport.',
16      code: `<Image
17  src="/photo.jpg"
18  alt="Gallery image"
19  width={800}
20  height={600}
21  loading="lazy"  // default
22/>`,
23      useCase: 'Below-the-fold images, galleries, blog content images',
24      pros: ['Saves bandwidth', 'Faster initial page load', 'Default behavior'],
25      cons: ['Slight delay when scrolling to image', 'Not ideal for hero images'],
26    },
27    {
28      id: 'eager',
29      name: 'Eager Loading',
30      icon: '',
31      color: 'green',
32      description: 'Images load immediately, regardless of position on page.',
33      code: `<Image
34  src="/photo.jpg"
35  alt="Important image"
36  width={800}
37  height={600}
38  loading="eager"
39/>`,
40      useCase: 'Images likely to be seen quickly, carousels, critical UI elements',
41      pros: ['No loading delay', 'Good for near-viewport images'],
42      cons: ['Uses more bandwidth', 'Can slow initial load'],
43    },
44    {
45      id: 'priority',
46      name: 'Priority Loading',
47      icon: '',
48      color: 'purple',
49      description: 'Highest priority loading. Preloaded in <head> for fastest display.',
50      code: `<Image
51  src="/hero.jpg"
52  alt="Hero banner"
53  width={1920}
54  height={1080}
55  priority  // preloads in <head>
56/>`,
57      useCase: 'Hero images, LCP elements, above-the-fold content',
58      pros: ['Fastest loading', 'Improves LCP score', 'Preloaded in head'],
59      cons: ['Use sparingly (1-2 per page)', 'Increases initial payload'],
60    },
61  ];
62
63  const active = strategies.find(s => s.id === activeStrategy);
64
65  return (
66    <div className="min-h-screen bg-gray-900 text-white p-8">
67      <div className="max-w-4xl mx-auto">
68        <h1 className="text-3xl font-bold mb-2">Image Loading Strategies</h1>
69        <p className="text-gray-400 mb-8">
70          Choose the right strategy for each image in your Next.js app
71        </p>
72
73        {/* Strategy Tabs */}
74        <div className="flex gap-4 mb-8">
75          {strategies.map(s => (
76            <button
77              key={s.id}
78              onClick={() => setActiveStrategy(s.id)}
79              className={`px-6 py-3 rounded-lg font-medium transition-colors ${
80                activeStrategy === s.id
81                  ? 'bg-blue-600 text-white'
82                  : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
83              }`}
84            >
85              {s.icon} {s.name}
86            </button>
87          ))}
88        </div>
89
90        {/* Active Strategy Details */}
91        {active && (
92          <div className="bg-gray-800 rounded-xl p-8">
93            <h2 className="text-2xl font-bold mb-2">
94              {active.icon} {active.name}
95            </h2>
96            <p className="text-gray-400 mb-6">{active.description}</p>
97
98            {/* Code Example */}
99            <div className="bg-gray-900 rounded-lg p-4 mb-6">
100              <pre className="text-sm text-green-400 overflow-x-auto">
101                {active.code}
102              </pre>
103            </div>
104
105            {/* Use Cases */}
106            <div className="mb-6">
107              <h3 className="font-semibold mb-2">When to use:</h3>
108              <p className="text-gray-400">{active.useCase}</p>
109            </div>
110
111            {/* Pros & Cons */}
112            <div className="grid grid-cols-2 gap-4">
113              <div>
114                <h3 className="font-semibold text-green-400 mb-2">Pros</h3>
115                <ul className="space-y-1">
116                  {active.pros.map((p, i) => (
117                    <li key={i} className="text-sm text-gray-400">• {p}</li>
118                  ))}
119                </ul>
120              </div>
121              <div>
122                <h3 className="font-semibold text-red-400 mb-2">Cons</h3>
123                <ul className="space-y-1">
124                  {active.cons.map((c, i) => (
125                    <li key={i} className="text-sm text-gray-400">• {c}</li>
126                  ))}
127                </ul>
128              </div>
129            </div>
130          </div>
131        )}
132
133        {/* Priority Guide */}
134        <div className="mt-8 bg-gray-800 rounded-xl p-6">
135          <h2 className="text-xl font-bold mb-4">Strategy Map</h2>
136          <div className="space-y-3">
137            <div className="flex items-center gap-4 p-3 bg-purple-900/30 rounded-lg">
138              <span className="text-2xl">↯</span>
139              <div>
140                <span className="font-semibold text-purple-400">Priority</span>
141                <span className="text-gray-400 ml-2">→ Hero image, main banner (1-2 per page)</span>
142              </div>
143            </div>
144            <div className="flex items-center gap-4 p-3 bg-green-900/30 rounded-lg">
145              <span className="text-2xl">»</span>
146              <div>
147                <span className="font-semibold text-green-400">Eager</span>
148                <span className="text-gray-400 ml-2">→ Near-viewport images, carousel slides</span>
149              </div>
150            </div>
151            <div className="flex items-center gap-4 p-3 bg-blue-900/30 rounded-lg">
152              <span className="text-2xl">›</span>
153              <div>
154                <span className="font-semibold text-blue-400">Lazy</span>
155                <span className="text-gray-400 ml-2">→ Everything else (default)</span>
156              </div>
157            </div>
158          </div>
159        </div>
160      </div>
161    </div>
162  );
163}

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Styled-components est une bibliothèque pour :

  2. 2. Le design mobile-first signifie :

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée un fichier de variables CSS définissant un design system complet (couleurs, espacements, typographie, ombres)

  • Tri vertical

    Classe dans le bon ordre les étapes d'utilisation des CSS Modules dans Next.js :

  • Clique dans l'ordre

    Reconstitue la syntaxe de className avec des classes Tailwind CSS en JSX

  • Éditeur de code

    Crée une animation keyframes pour un effet fade-in et une transition au survol pour un bouton

Articles utiles