Spis treści
- Natywne lazy loading – od czego zacząć
- LCP – gdzie najłatwiej stracić pozycje
- CLS – cichy zabójca przy lazy loadingu
- Kiedy natywne lazy loading nie wystarcza
- Intersection Observer API – zaawansowana kontrola
- Lazy loading dla iframe, fontów i CSS
- WordPress – lazy loading włączony domyślnie, ale nie idealnie
- Testowanie lazy loading
- Kiedy nie używać lazy loadingu
- Kiedy lazy loading ma sens, a kiedy nie
Lazy loading brzmi prosto: ładujesz obraz dopiero, gdy użytkownik przewinie stronę w jego okolice. W teorii to jedna z najłatwiejszych optymalizacji wydajności. W praktyce wystarczy jeden źle ustawiony atrybut, żeby zamiast pomóc – zaszkodzić pozycjom w Google.
Skala problemu jest konkretna: strony na 90. percentylu wysyłają ponad 5 MB obrazów na desktop i mobile. Lazy loading może zredukować tę wagę o 40–70%. Problem w tym, że jeśli wrzucisz go na hero image – główny obraz w widocznym obszarze – możesz przesunąć LCP z 1,8 s na 3,5 s. A LCP powyżej 2,5 s to już czerwone światło w Google Search Console. Różnica między dobrze skonfigurowanym lazy loading a błędem to nie kilka procent, ale 27 punktów procentowych w odsetku stron z dobrym LCP.
Poniżej pokazuję, jak robić lazy loading bez uderzania w Core Web Vitals, bez chowania treści przed Googlebotem i bez efektów ubocznych, które zniwelują całą korzyść.
Natywne lazy loading – od czego zacząć
Od 2020 roku wszystkie główne przeglądarki obsługują atrybut loading. Chrome dodał go w wersji 77, Firefox w 75, Safari w 15.4. Wsparcie sięga ponad 95% globalnego ruchu. Nie potrzebujesz biblioteki JavaScript, żeby to wdrożyć.
Wystarczy dopisać loading="lazy" do znacznika obrazka lub iframe – np. <img src="zdjecie.webp" loading="lazy" alt="Opis" width="800" height="600">. Przeglądarka robi resztę. Na szybkim 4G próg wynosi około 1250 pikseli przed wejściem w obszar widoczny, na wolnym 3G – około 2500 pikseli. To bezpieczny margines: obraz zdąży się załadować, zanim użytkownik do niego dotrze. Według danych z Chromium 97,5% leniwie ładowanych obrazów na 4G jest w pełni gotowych w ciągu 10 ms od pojawienia się w widoku.
Trzy wartości atrybutu loading:
lazy– ładuje dopiero przed wejściem w obszar widoczny,eager– ładuje natychmiast (zachowanie domyślne),auto– wartość wycofana, nie używaj jej.
Oprócz loading warto ustawić decoding. Atrybut decoding="async" mówi przeglądarce, żeby dekodowała obraz w tle, bez blokowania głównego wątku. Na hero image możesz rozważyć decoding="sync" – dekodowanie na głównym wątku może minimalnie przyspieszyć wyświetlenie kosztem chwilowego blokowania. Dla wszystkich obrazów poniżej linii widoku stosuj decoding="async".
LCP – gdzie najłatwiej stracić pozycje
Najczęstszy i najkosztowniejszy błąd: lazy loading na hero image. Według danych z solid-web.com strony bez lazy loadingu na obrazie LCP osiągają dobry wynik LCP w 79% przypadków. Strony, które leniwie ładują LCP – tylko w 52%. Różnica 27 punktów procentowych.
Hero image, slider produktowy, logo w headerze – wszystko, co pojawia się na starcie strony, musi mieć loading="eager" (lub po prostu brak atrybutu loading). Googlebot nie przewija strony, więc jeśli obraz jest poza zasięgiem widoku na starcie, może w ogóle nie zostać zaindeksowany.
Jeśli chcesz dodatkowo przyspieszyć LCP, użyj fetchpriority="high" na najważniejszym obrazie. Nie przyspiesza go w stosunku do obrazów bez loading, ale podnosi jego priorytet względem innych zasobów. Dla obrazów poniżej linii widoku możesz użyć fetchpriority="low", żeby nie konkurowały z krytyczną treścią.
CLS – cichy zabójca przy lazy loadingu
Kiedy ładujesz obraz z loading="lazy", ale nie podajesz wymiarów, przeglądarka rezerwuje zero pikseli. W momencie gdy obraz w końcu się załaduje, pozostała treść skacze w dół. To CLS – wskaźnik, który Google bierze pod uwagę od 2021 roku jako czynnik rankingowy.
Rozwiązanie jest proste: zawsze podawaj width i height w HTML. Przeglądarka obliczy proporcje i zarezerwuje odpowiednią przestrzeń jeszcze przed pobraniem pliku. Żadnych skoków layoutu.
Jeśli używasz CSS, możesz wymusić proporcje przez aspect-ratio. Działa tak samo – przeglądarka wie, ile miejsca zarezerwować.
WordPress od wersji 5.5 sam dodaje atrybuty width i height do obrazów w treści, ale nie zawsze robi to dla obrazów dodanych przez motyw lub page builder. Warto to sprawdzić.
Kiedy natywne lazy loading nie wystarcza
Natywne loading="lazy" działa dobrze w większości przypadków, ale nie obsługuje kilku scenariuszy:
- Karuzela horyzontalna – progi wykrywania nie działają poprawnie w poziomym scrollu. Chrome 121 rozwiązał to dla obrazów, ale dla innych zasobów wciąż może być problem.
- Modal i lightbox – obrazy poza widokiem w momencie otwarcia modala mogą nie załadować się na czas.
- Precyzyjna kontrola progu – natywne rozwiązanie nie pozwala ustawić własnego marginesu.
- Placeholder z efektem blur – natywny lazy loading nie daje kontroli nad tym, co użytkownik widzi, zanim obraz się załaduje.
W tych przypadkach potrzebujesz Intersection Observer API.
Intersection Observer API – zaawansowana kontrola
Intersection Observer to natywne API przeglądarki dostępne od 2019 roku. Pozwala asynchronicznie obserwować, kiedy element pojawia się w widoku, i wtedy uruchomić akcję. W przeciwieństwie do nasłuchiwania zdarzenia scroll na głównym wątku, działa poza nim i nie powoduje spowolnień.
Podstawowa konfiguracja:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '0px 0px 200px 0px',
threshold: 0
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
Kluczowe parametry:
rootMargin– rozszerza obszar wykrywania. Wartość200pxoznacza, że callback zostanie wywołany, gdy element znajdzie się 200 pikseli przed wejściem w widok. To daje czas na załadowanie obrazu.threshold– procent widoczności elementu, który wyzwala callback. Domyślnie 0 (jakikolwiek piksel).- Po załadowaniu obrazu wywołaj
observer.unobserve(), żeby nie marnować zasobów na dalsze obserwowanie.
Uwaga na SEO: przy implementacji przez Intersection Observer pamiętaj, że prawdziwy URL musi być dostępny w src dla Googlebota. Google nie wykonuje JavaScriptu w pełni i nie przewija strony. Jeśli URL jest tylko w data-src, bot go nie zobaczy. Rozwiązanie: zostaw w src mały placeholder (przezroczysty GIF o wymiarach 1×1 piksel lub miniaturkę), a w data-src trzymaj docelowy URL. Googlebot odczyta src i zaindeksuje obraz.
Nie łącz loading="lazy" z Intersection Observer na tym samym obrazie – podwójne opóźnienie (double-gating) może sprawić, że obraz załaduje się później, niż gdybyś użył tylko jednego mechanizmu.
Lazy loading dla iframe, fontów i CSS
Obrazy to nie jedyne zasoby, które warto leniwie ładować.
Iframe
Osadzone filmy z YouTube, mapy, posty z Instagramu – każdy taki element to dodatkowe setki kilobajtów. YouTube embed waży około 500 KB JavaScriptu, Instagram około 100 KB, Spotify ponad 500 KB. Lazy loading na iframe działa tak samo jak na obrazach – wystarczy dodać loading="lazy", np. <iframe src="https://www.youtube.com/embed/..." loading="lazy" width="560" height="315"></iframe>.
Chrome stosował automatyczne lazy loading na iframe dla użytkowników z włączonym Data Saver i osiągał 2–3% oszczędności transferu. Na stronie chrome.com lazy loading oszczędził 10 sekund TTI dla osadzonego YouTube.
Jeśli chcesz pójść dalej, rozważ tzw. facade – statyczny element imitujący film, który zamienia się w prawdziwy player dopiero po kliknięciu. To rozwiązanie oszczędza cały ciężar YouTube API do momentu faktycznej interakcji.
Fonty
Wczytywanie fontów może blokować renderowanie tekstu i powodować skoki layoutu (przy font-display: swap). Praktyczne zasady:
- Używaj WOFF2 – kompresja Brotli daje o 30% mniejszy rozmiar niż WOFF. Eksperci zalecają ograniczenie się wyłącznie do tego formatu.
- Dodaj
preconnectdo źródeł fontów. Dla Google Fonts potrzebujesz dwóch: dofonts.googleapis.com(bez crossorigin) ifonts.gstatic.com(z crossorigin dla plików fontów). - Rozważ
font-display: optional– przeglądarka czeka maksymalnie 100 ms na font, potem używa systemowego. Zero skoków layoutu, za cenę tego, że użytkownicy z wolnym internetem zobaczą font systemowy zamiast docelowego. - Variable fonts pozwalają zastąpić kilka plików jednym, co redukuje liczbę zapytań.
content-visibility: auto
Nowe CSS dostępne od września 2025 dla wszystkich głównych przeglądarek (Baseline Newly Available). Właściwość content-visibility: auto powoduje, że elementy poza ekranem nie są renderowane – przeglądarka pomija ich layout, style i paint.
Na demo web.dev przejście z normalnego renderowania na content-visibility: auto skróciło czas renderowania z 232 ms do 30 ms. W rzeczywistych testach odnotowano redukcję kosztu renderowania o 50–80%.
W przeciwieństwie do display: none, content-visibility: auto pozostawia element w DOM i drzewie dostępności. Tylko go nie renderuje. Dzięki temu przeglądarka zna jego strukturę, ale nie marnuje cykli CPU na wyświetlanie.
Żeby uniknąć skoku layoutu, ustaw contain-intrinsic-size – przybliżony rozmiar elementu przed renderowaniem. Możesz użyć składni auto 500px, która zapamiętuje rzeczywisty rozmiar po pierwszym renderze.
Uwaga: nie stosuj content-visibility: auto do treści nad widokiem – opóźni to LCP. W Safari wyszukiwanie w tekście (Cmd+F) może nie znaleźć treści w niewidocznych sekcjach z tą właściwością.
WordPress – lazy loading włączony domyślnie, ale nie idealnie
WordPress od wersji 5.5 (sierpień 2020) domyślnie dodaje loading="lazy" do wszystkich obrazów w treści z wyjątkiem pierwszego. Automatycznie ustawia też decoding="async".
Problem: jeśli motyw w hero sekcji wyświetla obraz jako pierwszy w treści – WordPress go nie oznaczy jako lazy. Jeśli jednak dodajesz obraz hero jako osobny blok, który ląduje w treści przed pierwszym akapitem – teoretycznie WordPress powinien go pominąć przy lazy loadingu. W praktyce wiele motywów i page builderów dodaje obrazy w sposób, który WordPress klasyfikuje jako „pierwszy", ale niekoniecznie jest to ten obraz, który faktycznie jest LCP. Zawsze warto sprawdzić w narzędziach deweloperskich, czy hero image nie ma loading="lazy".
Jeśli motyw dodaje lazy loading do hero image, musisz to nadpisać. W wielu motywach można to zrobić przez filtr lub bezpośrednio w plikach szablonu – ustawiając 'loading' => 'eager' i 'fetchpriority' => 'high'.
Testowanie lazy loading
Po wdrożeniu lazy loadingu trzeba sprawdzić dwie rzeczy: czy działa poprawnie i czy nie szkodzi indeksowaniu.
Sprawdzenie działania
Otwórz Narzędzia Deweloperskie w Chrome (F12), przejdź do zakładki Network i odfiltruj obrazy. Załaduj stronę od nowa. Powinieneś zobaczyć, że ładują się tylko obrazy w widocznym obszarze. Gdy przewijasz, pojawiają się kolejne. Jeśli wszystkie obrazy ładują się na starcie – lazy loading nie działa.
W panelu Performance możesz sprawdzić LCP: najedź na znacznik LCP i zobacz, czy obraz hero był ładowany jako pierwszy, czy z opóźnieniem.
Weryfikacja SEO
Google Search Console → URL Inspection Tool. Wklej URL strony i zobacz rendered HTML. Sprawdź, czy URL obrazów znajdują się w atrybucie src. Jeśli obrazy mają tylko data-src, Googlebot ich nie zobaczy.
Drugi test: wyłącz JavaScript w przeglądarce i przeładuj stronę. To symuluje, jak Googlebot widzi stronę na pierwszym etapie indeksowania. Jeśli obrazy zniknęły – masz problem z indeksowaniem.
Trzeci: użyj PageSpeed Insights lub Lighthouse z linii poleceń, żeby sprawdzić, czy narzędzie nie raportuje ostrzeżenia „Defer offscreen images".
Monitorowanie w czasie
Core Web Vitals zmieniają się po wdrożeniu optymalizacji. Google Search Console pokazuje dane z CrUX dla realnych użytkowników. DebugBear lub inne narzędzia RUM pozwalają śledzić LCP i CLS w czasie i ustawić alerty na regresje. Warto porównywać wyniki z kilku źródeł – każde daje trochę inną perspektywę.
Audyty warto powtarzać przynajmniej raz na kwartał. Zmieniają się przeglądarki, aktualizują silniki renderujące, a na stronie pojawiają się nowe elementy, które mogą przypadkiem dostać lazy loading.
Kiedy nie używać lazy loadingu
Lazy loading to narzędzie, nie dogmat. Są sytuacje, w których lepiej go pominąć:
- Obrazy LCP (hero, slider, logo) – te elementy muszą być widoczne natychmiast.
- Treść indeksowana przez Google – Googlebot nie scrolluje, więc jeśli chcesz, żeby obraz został zaindeksowany, musi być dostępny bez JavaScriptu.
- Elementy śledzące i analityka – hidden iframe do analityki nie powinien być lazy loaded.
- Elementy pod widokiem, które użytkownik prawie na pewno zobaczy – na krótkich stronach lazy loading nie ma sensu. Oszczędność transferu będzie znikoma, a ryzyko popsucia CLS – realne.
- Drukowanie strony – przeglądarki ładują wszystkie obrazki niezależnie od
loading, więc to akurat nie problem.
Kiedy lazy loading ma sens, a kiedy nie
Jeśli prowadzisz sklep internetowy z setkami zdjęć produktów – lazy loading jest dla ciebie obowiązkowy. Zmierzysz realną redukcję transferu (40–70%) i poprawę LCP dla pierwszego wejścia, o ile nie złapiesz się na pułapce lazy loadowania hero image.
Jeśli masz stronę wizytówkę z pięcioma zdjęciami – lazy loading może nie mieć żadnego wymiernego sensu. Ryzyko popełnienia błędu (CLS, lazy na LCP) przewyższa korzyść z odłożenia 200 KB obrazów. W tym scenariuszu lepiej skompresować obrazy i ustawić im width/height, a lazy loading pominąć.
Decyzja sprowadza się do prostej kalkulacji: czy liczba obrazów poniżej linii widoku uzasadnia ryzyko? Jeśli tak – natywny loading="lazy" i żadnych bibliotek JS. Jeśli nie – lepiej zostawić wszystko jako loading="eager" i skupić się na formatach i kompresji.
Masz problemy z implementacją lazy loading (leniwego ładowania) na swojej stronie WordPress? Chętnie pomożemy Ci wdrożyć optymalne rozwiązanie, które poprawi wydajność i Core Web Vitals Twojej witryny. Skontaktuj się z nami, aby uzyskać profesjonalne wsparcie w optymalizacji.