Dlaczego obrazy tak mocno wpływają na wydajność?
Next.js oferuje komponent next/image, który automatyzuje większość optymalizacji. Nie zawsze jest jednak najlepszym wyborem. W tym artykule porównuję trzy podejścia: next/image, natywny HTML z srcset i dedykowane CDN do obrazów.
Komponent next/image: co robi pod maską
next/image to wrapper nad tagiem <img>, który automatycznie:
- Konwertuje format: domyślnie serwuje WebP, a AVIF po dodaniu go do
images.formats. - Skaluje rozmiar: generuje warianty dla różnych viewportów.
- Lazy loading: ładuje obrazy dopiero, gdy są blisko viewportu.
- Zapobiega CLS: wymaga podania
widthiheightalbo użyciafillw kontenerze o stałym formacie. - Blur placeholder: wyświetla rozmyty podgląd podczas ładowania, ale dla zdalnych obrazów zwykle wymaga własnego
blurDataURL.
Podstawowe użycie
Prop sizes: podstawa prawidłowego skalowania
sizes informuje przeglądarkę, jaką szerokość będzie miał obraz na różnych viewportach. Przy obrazach responsywnych bez tej informacji przeglądarka zakłada zwykle 100vw, więc może pobrać wariant większy niż potrzebny, nawet jeśli obraz zajmuje przykładowo 1/3 strony.
Prop fill: obrazy responsywne bez wymiarów
Gdy nie znasz wymiarów (np. zdjęcia z CMS), użyj fill z kontenerem o określonym aspect ratio:
Konfiguracja domen zewnętrznych
Aby next/image mógł optymalizować obrazy z zewnętrznych źródeł, musisz dodać je do konfiguracji, ale nie rób szerokiego wildcarda na całą domenę, jeśli obrazy leżą tylko w jednym katalogu.
qualities ma znaczenie od Next.js 16. Bez allowlisty ktoś mógłby wymusić wiele wariantów tego samego obrazu przez parametry jakości. Ustal 2-4 wartości, których naprawdę używasz. minimumCacheTTL ogranicza koszt ponownej optymalizacji, ale nie traktuj go jak magicznego przyspieszacza. Pierwsze żądanie nowego wariantu nadal płaci koszt transformacji, a potem dopiero działa cache. Zbyt wysokie TTL utrudnia też podmianę obrazu, bo Next.js nie daje prostego mechanizmu ręcznego czyszczenia cache obrazów.
Cache, koszty i limity Image Optimization
next/image optymalizuje obrazy na żądanie, co jest bardzo wygodne wygodne, ale ma pewne konsekwencje o których warto pamiętać:
pierwsze wejście na nowy wariant może być wolniejsze, ponieważ serwer generuje obraz
każdy rozmiar, format i quality to osobny wariant do cache
szerokie
sizesi przypadkowequalitypotrafią pomnożyć liczbę wariantówna platformach serverless lub managed hosting liczba optymalizacji może mieć koszt
przy bardzo dużej bibliotece zdjęć dedykowany image CDN bywa tańszy i stabilniejszy
Dlatego w projektach e-commerce i marketplace nie wystarczy użyć masowo next/image. Najpierw trzeba policzyć, ile wariantów realnie wygenerujesz: liczba obrazów razy liczba szerokości razy liczba formatów razy liczba jakości. Jeśli masz 30 000 zdjęć produktów, next/image bez dyscypliny w sizes, qualities i źródłach, odbije się to na kosztach.
Przykład ograniczenia wariantów:
W sytuacji, w której masz mało obrazów hero i znaczny ruch mobilny, AVIF może zmniejszyć transfer, ale koduje się wolniej. Jeśli masz tysiące rzadko odwiedzanych zdjęć produktowych, WebP może być bardziej rozsądnym domyślnym formatem.
Bezpieczne źródła obrazów
Najgorsza konfiguracja to hostname: '**' albo szerokie dopuszczenie całego zewnętrznego hosta bez ścieżki, ponieważ wtedy komponent obrazu zaczyna działać jak publiczny proxy optimizer. Bezpieczniejszy wariant ogranicza protokół, host, pathname i query:
Jeśli CMS generuje podpisane URL-e z query stringiem, zdecyduj, czy search ma być pusty, konkretny, czy pominięty. Pusty search: '' blokuje dowolne parametry, a pominięcie search dopuszcza dowolny query string, co bywa potrzebne, ale zwiększa powierzchnię nadużyć i liczbę wariantów cache.
Natywny <img> z srcset: kiedy wystarczy
Nie każdy projekt potrzebuje next/image. Jeśli masz statyczną stronę, kontrolujesz warianty obrazów i nie potrzebujesz automatycznej konwersji formatów, natywny HTML jest prostszy i daje pełną kontrolę.
Kiedy wybrać natywny srcset
- Static export (
output: 'export'):next/imagedomyślnie wymaga serwera do optymalizacji, chyba że skonfigurujesz zewnętrzny loader. - Pełna kontrola nad wariantami: sam generujesz dokładne rozmiary przez skrypt.
- Art direction: na mobile chcesz inny kadr niż na desktopie.
- Minimalizm: nie chcesz dodatkowej warstwy abstrakcji.
srcset odpowiada za "który rozmiar tego samego obrazu pobrać”, a <picture> odpowiada na “który obraz pobrać”.
Tego nie zastąpisz samym sizes, ponieważ sizes nie zmienia kadru. Jeśli mobilny hero wymaga ciasnego portretowego kadru, a desktop szerokiej sceny, <picture> jest prostszym i bardziej przewidywalnym narzędziem.
CDN do obrazów: Cloudinary, Imgix, Cloudflare Images
Dedykowane do obrazów oferują transformacje w locie: skalowanie, kadrowanie, filtry, konwersję formatów bez generowania wariantów w build time.
Integracja z next/image przez custom loader
CDN to dobre rozwiązanie, jeśli źródło obrazów jest zmienne i duże: CMS, marketplace, e-commerce, profile użytkowników, galerie, feedy. Wtedy problemem nie jest samo LCP na jednej stronie, ale operacyjny koszt tysięcy transformacji, czyszczenia cache, kadrowania, watermarków, prywatnych assetów i wariantów dla wielu kanałów.
Przy CMS-ach masz trzy rozsądne warianty:
| Źródło | Najlepszy wybór | Dlaczego |
|---|---|---|
| lokalne grafiki marketingowe | statyczny import + next/image | automatyczne wymiary i blur, mało wariantów |
| CMS z własnym image CDN, np. Sanity | loader albo natywne URL-e CDN | CMS już umie transformować obrazy |
| e-commerce z tysiącami zdjęć | dedykowany image CDN | koszty, cache i transformacje są poza serwerem Next.js |
| static export | <picture> / srcset albo CDN loader | brak serwera optymalizującego |
Nie dubluj optymalizacji, bo jeśli Cloudinary albo Sanity już zwraca f_auto, q_auto i właściwą szerokość, przepuszczanie tego jeszcze przez domyślny optimizer Next.js nie ma sensu. W takiej architekturze użyj custom loadera albo natywnych URL-i CDN.
Porównanie trzech podejść
| Kryterium | next/image | Natywny srcset | CDN (Cloudinary/Imgix) |
|---|---|---|---|
| Konwersja formatu | Automatyczna wg konfiguracji | Ręczna | Automatyczna |
| Skalowanie | Automatyczne | Ręczne warianty | On-the-fly |
| Lazy loading | Wbudowane | loading="lazy" | loading="lazy" |
| CLS prevention | Wymuszony width/height | Ręczne | Ręczne |
| Blur placeholder | Automatyczny dla części importów statycznych | Ręczny (LQIP) | Zależy od CDN |
| Koszt | Za serwer Next.js | Zero | Opłata za CDN |
| Static export | Wymaga custom loader | Działa | Działa |
| Kontrola | Średnia | Pełna | Pełna |
| Setup | Minimalny | Pracochłonny | Średni |
Jak wybrać podejście
To może teraz najprostszy sposób podjęcie decyzji.
- Masz standardową aplikację Next.js z serwerem albo Vercel? Zacznij od
next/image. - Robisz
output: 'export'bez zewnętrznego loadera? Wybierz<picture>isrcset. - Masz tysiące obrazów z CMS-a, marketplace albo e-commerce? Wybierz image CDN lub loader pod CDN.
- Potrzebujesz różnych kadrów na mobile i desktopie? Użyj
<picture>, nawet jeśli reszta obrazów idzie przeznext/image. - Masz jeden hero i kilka grafik marketingowych? Statyczne importy +
next/imagesą najprostsze i zwykle wystarczają. - Masz już zoptymalizowane URL-e z Sanity, Cloudinary albo Imgix? Nie optymalizuj ich drugi raz bez powodu.
Najlepsze praktyki niezależnie od podejścia
1. LCP image: preload, loading="eager" albo fetchPriority="high"
W Next.js 16 prop preload zastąpił przestarzały priority. Jeśli wiesz, który obraz jest , użyj preload, ale tylko na jednym obrazie above-the-fold. Nadmiarowy preload, np. cały rząd kart produktów, konkuruje z krytycznym CSS/JS i pogarsza LCP.
Gdy kandydat do LCP zależy od viewportu albo masz kilka podobnie ważnych obrazów, bezpieczniej użyć loading="eager" albo fetchPriority="high".
2. Poprawny alt dla SEO i dostępności
3. Unikaj layout shift z aspect ratio
4. Mierz realny efekt
Po zmianie obrazów sprawdź trzy rzeczy:
- czy LCP wskazuje właściwy element w PageSpeed Insights lub WebPageTest?
- czy pobrany rozmiar obrazu odpowiada layoutowi, a nie pełnej szerokości ekranu?
- czy CLS jest zerowy dzięki
width/height,fillz aspect ratio albo stabilnemu kontenerowi?
W DevTools otwórz Network, włącz kolumny Resource Size i Content-Type, a potem porównaj mobile i desktop. Jeśli karta produktu o szerokości 280 px pobiera obraz 1200 px, problemem zwykle jest błędne sizes.

