Strategie ładowania obrazów (lazy, eager, priority) w Next.js 16
Kurs Next.js · Moduł 3: Stylowanie i optymalizacja wizualna
W Metropolis Quantum, system transportowy miasta jest zorganizowany według różnych priorytetów. Pojazdy ratunkowe mają najwyższy priorytet i zawsze mają pierwszeństwo przejazdu. Transport publiczny ma średni priorytet, a pojazdy prywatne najniższy. To inteligentne zarządzanie ruchem zapewnia, że najważniejsze zasoby zawsze docierają do celu najszybciej, podczas gdy mniej krytyczne mogą poczekać.
Podobnie w aplikacjach internetowych, różne obrazy mają różne poziomy ważności. Obraz główny (hero image) strony powinien załadować się natychmiast, podczas gdy obrazy umieszczone niżej na stronie mogą być ładowane tylko wtedy, gdy użytkownik przewinie do nich. Next.js 16 oferuje różne strategie ładowania obrazów, które pozwalają dostosować sposób ładowania obrazów do ich ważności i miejsca na stronie.
Trzy główne strategie ładowania obrazów
W Next.js 16 mamy do dyspozycji trzy główne strategie ładowania obrazów:
- Lazy (leniwe) - domyślna strategia, obrazy są ładowane tylko wtedy, gdy zbliżają się do widocznego obszaru (viewport)
- Eager (gorliwe) - obrazy są ładowane natychmiast, niezależnie od ich pozycji na stronie
- Priority (priorytetowe) - obrazy są ładowane z najwyższym priorytetem, idealnie dla obrazów "above the fold". W Next.js 16 włączasz tę strategię atrybutem
preload, który zastąpił przestarzały atrybutpriority
Każda z tych strategii ma swoje zastosowanie i wpływ na wydajność strony.
Lazy Loading (leniwe ładowanie)
Leniwe ładowanie to domyślna strategia dla komponentu Image w Next.js 16. Działa ona zgodnie z prostą zasadą:
Ładuj tylko to, co użytkownik aktualnie widzi lub wkrótce zobaczy.
Jak działa lazy loading?
- Next.js automatycznie opóźnia ładowanie obrazów, które znajdują się poza widocznym obszarem (viewport)
- Obrazy są ładowane dopiero wtedy, gdy użytkownik przewinie stronę i zbliżą się do widocznego obszaru
- W tle używana jest nowoczesna funkcja przeglądarki
loading="lazy", z dodatkowymi optymalizacjami
Przykład użycia 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 - zdjęcie ${index + 1}`}
11 fill
12 style={{ objectFit: 'cover' }}
13 // Nie trzeba dodawać loading="lazy" - jest to wartość domyślna
14 />
15 </div>
16 ))}
17 </div>
18 );
19}Zalety lazy loading:
- Szybsze wstępne ładowanie strony - tylko krytyczne obrazy są ładowane natychmiast
- Mniejsze zużycie danych - obrazy, do których użytkownik nigdy nie dotrze, nie są ładowane
- Lepsza wydajność - przeglądarka może skupić zasoby na elementach, które są aktualnie ważne
Scenariusze, w których warto używać lazy loading:
- Długie strony z wieloma obrazami
- Galerie zdjęć
- Listy produktów
- Karuzele obrazów
- Strony z nieskończonym przewijaniem
Eager Loading (gorliwe ładowanie)
Strategia eager loading jest przeciwieństwem leniwego ładowania. Zamiast czekać, aż obrazy zbliżą się do widocznego obszaru, obrazy są ładowane natychmiast podczas ładowania strony.
Jak ustawić 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}Zalety eager loading:
- Natychmiastowa dostępność - wszystkie obrazy są dostępne od razu po załadowaniu strony
- Brak opóźnień - użytkownik nie musi czekać na ładowanie obrazów podczas przewijania
- Bardziej przewidywalne zachowanie - wszystkie zasoby są ładowane w jednym cyklu
Scenariusze, w których warto używać eager loading:
- Krótkie strony z niewielką liczbą obrazów
- Strony, gdzie wszystkie obrazy są ważne dla kontekstu
- Aplikacje, gdzie płynne przewijanie bez opóźnień jest kluczowe
- Aplikacje offline-first, które chcą załadować wszystkie zasoby od razu
Priority Loading (priorytetowe ładowanie)
Priority loading to specjalna strategia dla najważniejszych obrazów na stronie, szczególnie tych widocznych "above the fold" (w górnej części strony, widocznej bez przewijania).
Jak ustawić priority loading:
1import Image from 'next/image';
2
3export default function QuantumHero() {
4 return (
5 <Image
6 src="/images/quantum-hero.jpg"
7 alt="Witaj w Metropolis Quantum"
8 width={1920}
9 height={1080}
10 preload
11 />
12 );
13}Gdy używasz atrybutu preload (w Next.js 15 i starszych nazywał się priority):
- Next.js dodaje do
<head>znacznik<link rel="preload">dla tego obrazu - Przeglądarka zaczyna pobierać obraz od razu, zanim dotrze do niego w treści strony
- Największy element strony (LCP) pojawia się szybciej
Stary atrybut priority w Next.js 16 wciąż działa, ale jest przestarzały. Dokumentacja radzi też, by preload stosować tylko wtedy, gdy obraz na pewno jest elementem LCP; w pozostałych przypadkach wybierz loading="eager" albo fetchPriority="high".
Zalety priority loading:
- Niższe LCP (Largest Contentful Paint) - kluczowa metryka Core Web Vitals
- Szybsze wyświetlanie najważniejszych obrazów - lepsza percepcja wydajności
- Optymalizacja dla Core Web Vitals - pozytywny wpływ na SEO
Scenariusze, w których warto używać priority:
- Główne obrazy (hero images) stron
- Logo marki
- Obrazy produktów na stronach szczegółowych
- Awatary użytkowników w aplikacjach społecznościowych
- Wszelkie obrazy, które stanowią główną zawartość strony
Kluczowe uwagi dotyczące atrybutu preload:
- Używaj
preloadoszczędnie - tylko dla najważniejszych obrazów - Dokumentacja Next.js zaleca
preloadtylko dla obrazu LCP (Largest Contentful Paint), a nie dla kilku obrazów, które mogą nim być zależnie od szerokości ekranu - Zbyt wiele obrazów z
preloadmoże paradoksalnie zmniejszyć wydajność
Porównanie strategii ładowania
| Strategia | Atrybut | Przypadki użycia | Wpływ na wydajność | |-----------|---------|------------------|-------------------| | Lazy | loading="lazy" (domyślne) | Obrazy poniżej "fold", galerie, listy | Szybsze ładowanie początkowe, oszczędność danych | | Eager | loading="eager" | Małe strony, wszystkie obrazy ważne | Wolniejsze ładowanie początkowe, lepsza responsywność podczas przewijania | | Priority | preload (dawniej priority) | Obrazy "above the fold", hero images, główna zawartość | Lepsze LCP, szybsze wyświetlanie kluczowych obrazów |
Zaawansowane strategie ładowania obrazów
Poza podstawowymi strategiami, Next.js 16 pozwala na bardziej zaawansowane podejścia do ładowania obrazów:
1. Preload ważnych obrazów z useRouter
Czasami chcemy wstępnie załadować obrazy dla strony, na którą użytkownik prawdopodobnie przejdzie:
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 obrazu szczegółowego produktu po najechaniu myszą
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 bg-opacity-60 p-4">
39 <h3 className="text-white font-bold">{product.title}</h3>
40 </div>
41 </div>
42 );
43}2. Dynamiczne przełączanie priority w zależności od widoczności
Możemy dynamicznie nadawać priorytet obrazom, które są aktualnie widoczne:
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. Progresywne ładowanie obrazów
Można łączyć strategie, aby najpierw wyświetlić obraz niskiej jakości, a następnie zastąpić go wersją wysokiej jakości:
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 ładowany natychmiast
22 />
23
24 {/* Obraz wysokiej jakości */}
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}Optymalizacja obrazów dla różnych urządzeń
Strategie ładowania można łączyć z technikami dostosowywania obrazów do różnych urządzeń:
1. Responsywne obrazy z different źródłami
1import Image from 'next/image';
2
3export default function ResponsiveHero() {
4 return (
5 <>
6 {/* Obraz dla urządzeń mobilnych */}
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 // Priorytet dla głównego obrazu
14 />
15 </div>
16
17 {/* Obraz dla tabletów */}
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 {/* Obraz dla desktopów */}
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. Adaptacyjne ładowanie w zależności od łącza
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 // Wykryj jakość połączenia
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 // Nasłuchuj zmian w jakości połączenia
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="Adaptacyjny obraz"
41 fill
42 style={{ objectFit: 'cover' }}
43 loading={connectionType === 'high' ? 'eager' : 'lazy'}
44 quality={connectionType === 'high' ? 90 : 50}
45 />
46 </div>
47 );
48}Pamiętaj, że w Next.js 16 obie wartości quality (90 i 50) muszą znaleźć się na liście images.qualities w next.config. Domyślnie ta lista zawiera tylko 75, więc bez jej rozszerzenia oba warianty dostaną jakość 75.
Implementacja zaawansowanych strategii ładowania w realnej aplikacji
Przykład: Strona produktowa z różnymi strategiami ładowania
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 {/* Główny obraz produktu - 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 Dodaj do koszyka
34 </button>
35 </div>
36 </div>
37
38 {/* Galeria dodatkowych zdjęć - lazy loading (domyślne) */}
39 <div className="mt-12">
40 <h2 className="text-2xl font-bold mb-6">Galeria produktu</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} - zdjęcie ${index + 1}`}
47 fill
48 style={{ objectFit: 'cover' }}
49 sizes="(max-width: 768px) 50vw, 25vw"
50 // Domyślne lazy loading
51 />
52 </div>
53 ))}
54 </div>
55 </div>
56
57 {/* Podobne produkty - eager loading dla pierwszego elementu */}
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 tylko dla pierwszego produktu
40 loading={index === 0 ? 'eager' : 'lazy'}
41 sizes="(max-width: 768px) 50vw, 25vw"
42 />
43 <div className="absolute bottom-0 w-full bg-black bg-opacity-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}Wpływ strategii ładowania na Core Web Vitals
Core Web Vitals to zestaw metryk Google, które mierzą doświadczenie użytkownika na stronach internetowych. Wybór właściwej strategii ładowania obrazów ma bezpośredni wpływ na te metryki:
1. LCP (Largest Contentful Paint)
LCP mierzy czas ładowania największego elementu widocznego w viewport, którym często jest obraz.
Wpływ strategii ładowania:
- Priority loading dla głównego obrazu może znacząco poprawić LCP
- Eager loading dla wszystkich obrazów może pogorszyć LCP przez rozproszenie zasobów
- Lazy loading dla obrazów poniżej "fold" nie wpływa na LCP, ale poprawia ogólną wydajność
2. CLS (Cumulative Layout Shift)
CLS mierzy niestabilność układu strony podczas ładowania.
Wpływ strategii ładowania:
- Wszystkie strategie mogą minimalizować CLS, jeśli obrazy mają określone wymiary
- Priority loading z
placeholder="blur"zapewnia najlepsze CLS, ponieważ rezerwuje przestrzeń natychmiast - Zawsze określaj
widthiheightlub używajfillz kontenerem o określonych wymiarach
3. FID (First Input Delay)
FID mierzy czas, jaki mija od pierwszej interakcji użytkownika do momentu, gdy przeglądarka może na nią zareagować.
Wpływ strategii ładowania:
- Lazy loading może poprawić FID przez zmniejszenie pracy przeglądarki podczas ładowania
- Eager loading może pogorszyć FID przez zwiększenie obciążenia głównego wątku
- Priority loading dla kluczowych obrazów może być kompromisem
Pomiary i optymalizacja strategii ładowania
Aby upewnić się, że wybrane strategie ładowania działają optymalnie, warto mierzyć ich wpływ:
1. Narzędzia do pomiaru wydajności
- Lighthouse - kompleksowe narzędzie do analizy wydajności
- Chrome DevTools Performance - szczegółowa analiza ładowania zasobów
- WebPageTest - testowanie wydajności w różnych warunkach
- Core Web Vitals w Google Search Console
2. A/B testy strategii ładowania
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 // Przypisz użytkownika do grupy A lub B
11 const group = Math.random() > 0.5 ? 'A' : 'B';
12 setTestGroup(group);
13
14 // Zapisz dane telemetryczne
15 const trackImageLoad = (time) => {
16 // Wysyłanie danych do systemu analitycznego
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 // Nasłuchuj na zdarzenie load obrazu
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'} // Grupa A używa preload, grupa B nie
47 />
48 </div>
49 );
50}Dobre praktyki dotyczące strategii ładowania obrazów
Używaj
preloadtylko dla obrazów above-the-fold - najlepiej dla jednego obrazu LCP na stronieDomyślnie używaj lazy loading - pozwala to oszczędzić zasoby i przyspieszyć początkowe ładowanie strony
Monitoruj metryki Web Vitals - regularnie sprawdzaj, jak strategie ładowania wpływają na wydajność
Dostosuj strategie do rodzaju strony:
- Strony produktowe:
preloaddla głównego zdjęcia produktu - Strony kategorii:
lazydla większości obrazów,eagerdla pierwszych kilku - Strona główna:
preloaddla hero image,lazydla pozostałych
- Strony produktowe:
Uwzględnij urządzenia użytkowników:
- Użytkownicy mobilni często mają wolniejsze połączenia - jeszcze ważniejsze jest użycie
lazyi mniejszych obrazów - Użytkownicy desktopowi mogą skorzystać z
eagerdla lepszego doświadczenia przewijania
- Użytkownicy mobilni często mają wolniejsze połączenia - jeszcze ważniejsze jest użycie
Używaj placeholderów dla lepszego doświadczenia podczas ładowania:
- Dla obrazów z
preload- pokazuj placeholder typublurlubempty - Dla obrazów z
lazy- używajblurdla płynnego przejścia
- Dla obrazów z
Scenariusze dla różnych strategii
1. Strona główna 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="Witaj w Quantum Shop"
9 fill
10 style={{ objectFit: 'cover' }}
11 preload
12 />
13 </div>
14
15 {/* Promocje - eager dla pierwszych dwóch */}
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 {/* Kategorie produktów - lazy (domyślne) */}
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 // lazy loading domyślne
40 />
41 </div>
42 ))}
43 </div>
44 </div>
45 );
46}2. Blog z długimi artykułami
1export default function BlogPost({ post }) {
2 return (
3 <article className="max-w-3xl mx-auto py-8">
4 {/* Główny obraz artykułu - 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 // lazy loading domyślne dla obrazów w treści
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 {/* Powiązane artykuły - wszystkie lazy */}
44 <div className="mt-12">
45 <h2 className="text-2xl font-bold mb-6">Powiązane artykuły</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 // Wszystkie lazy (domyślne)
55 />
56 </div>
57 ))}
58 </div>
59 </div>
60 </article>
61 );
62}Podsumowanie
Wybór odpowiedniej strategii ładowania obrazów jest kluczowy dla optymalizacji wydajności i doświadczenia użytkownika. Podobnie jak w systemie transportowym Metropolis Quantum, gdzie pojazdy o różnym priorytecie poruszają się z różną pilnością, tak w aplikacji webowej obrazy mogą być ładowane z różnymi strategiami w zależności od ich ważności:
Priority loading - dla najważniejszych obrazów "above the fold", które mają bezpośredni wpływ na LCP i pierwsze wrażenie użytkownika
Eager loading - dla obrazów, które są mniej krytyczne, ale wciąż ważne, i prawdopodobnie będą potrzebne wkrótce po załadowaniu strony
Lazy loading - dla większości obrazów, szczególnie tych poniżej "fold", które będą ładowane tylko wtedy, gdy użytkownik do nich dotrze
Wybierając strategię ładowania dla swoich obrazów, uwzględnij:
- Widoczność obrazu bez przewijania
- Wpływ na kluczowe metryki wydajności
- Typ zawartości i jej ważność
- Urządzenia i łącza internetowe Twoich użytkowników
Dzięki odpowiedniemu zastosowaniu strategii ładowania obrazów, możesz stworzyć aplikację, która ładuje się szybko, efektywnie zarządza zasobami i zapewnia płynne doświadczenie użytkownika - podobnie jak inteligentny system transportowy Metropolis Quantum, który zapewnia płynny przepływ pojazdów o różnych priorytetach.
W następnym rozdziale zajmiemy się optymalizacją czcionek i komponentem Font, co pozwoli nam jeszcze bardziej poprawić wydajność i estetykę naszej aplikacji Next.js 16.
Kod do tej lekcji: 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}Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Styled-components to biblioteka dla:
2. Mobile-first design oznacza:
Zadania praktyczne w grze
- Edytor kodu
Utwórz plik z CSS variables definiującymi kompletny system designu (kolory, spacing, typography, shadows)
- Układanie w pionie
Ułóż kroki użycia CSS Modules w Next.js w prawidłowej kolejności:
- Klikanie w kolejności
Ułóż składnię className z klasami Tailwind CSS w JSX
- Edytor kodu
Utwórz keyframes animation dla fade-in efektu i hover transition dla przycisku