Przejdź do treści

next/image vs srcset vs CDN: ostateczny poradnik optymalizacji obrazów w Next.js

next/image, srcset czy CDN? LCP, sizes, preload, qualities, remotePatterns, cache, static export i koszty obrazów w Next.js.

Maciej Sala

Founder StriveLab

7 min czytaniaOpublikowano 10 kwietnia 2026 (Aktualizacja 26 czerwca 2026)

Dlaczego obrazy wpływają na LCP, CLS i Core Web Vitals?

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.

next/image w Next.js: 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 width i height albo użycia fill w 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 next/image

Code
import Image from 'next/image'
 
// Obraz statyczny z importu: automatyczne wymiary i blur placeholder
import heroImage from '@/public/images/hero.jpg'
 
export default function HeroSection() {
  return (
    <Image
      src={heroImage}
      alt="Hero banner strony StriveLab"
      preload // Gdy to jednoznaczny LCP image
      placeholder="blur" // Automatyczny blur z importu
      className="h-auto w-full"
    />
  )
}
Code
// Obraz dynamiczny z URL-a: wymiary wymagane
export default function ProductCard({ product }: { product: Product }) {
  return (
    <Image
      src={product.imageUrl}
      alt={product.name}
      width={400}
      height={300}
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
    />
  )
}

Prop sizes w next/image: 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.

Code
// Blog post: obraz pełnej szerokości na mobile, połowa na desktop
<Image
  src="/images/blog/post-cover.jpg"
  alt="Cover"
  width={1200}
  height={630}
  sizes="(max-width: 768px) 100vw, 50vw"
/>
 
// Siatka produktów: 100% na mobile, 50% na tablecie, 25% na desktop
<Image
  src={product.image}
  alt={product.name}
  width={400}
  height={400}
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
/>

Prop fill w next/image: 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:

Code
<div className="relative aspect-video">
  <Image
    src={post.coverImage}
    alt={post.title}
    fill
    className="rounded-lg object-cover"
    sizes="(max-width: 768px) 100vw, 60vw"
  />
</div>

Konfiguracja domen zewnętrznych dla next/image

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.

Code
// next.config.ts
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        pathname: '/images/**',
        search: '',
      },
      {
        protocol: 'https',
        hostname: 'cdn.sanity.io',
        pathname: '/images/**',
      },
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
        pathname: '/photo-**',
      },
    ],
    formats: ['image/avif', 'image/webp'],
    qualities: [50, 75, 90],
    minimumCacheTTL: 60 * 60 * 24 * 30,
  },
}
 
export default nextConfig

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 w Next.js

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 sizes i przypadkowe quality potrafią pomnożyć liczbę wariantów

  • na 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:

Code
// next.config.ts
const nextConfig = {
  images: {
    deviceSizes: [360, 640, 768, 1024, 1280, 1536],
    imageSizes: [96, 160, 320],
    qualities: [50, 75],
    formats: ['image/webp'],
  },
}

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 w Next.js

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:

Code
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        pathname: '/products/**',
        search: '',
      },
    ],
  },
}

Jeśli CMS generuje podpisane URL-e z parametrami zapytania, zdecyduj, czy search ma być pusty, konkretny, czy pominięty. Pusty search: '' blokuje dowolne parametry, a pominięcie search dopuszcza dowolne parametry zapytania, co bywa potrzebne, ale zwiększa powierzchnię nadużyć i liczbę wariantów cache.

Natywny <img> z srcset: kiedy wystarczy zamiast next/image?

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ę.

Code
<picture>
  <!-- AVIF: najlżejszy format dla nowoczesnych przeglądarek -->
  <source
    type="image/avif"
    srcset="
      /images/hero-400.avif   400w,
      /images/hero-800.avif   800w,
      /images/hero-1200.avif 1200w
    "
    sizes="(max-width: 768px) 100vw, 50vw"
  />
  <!-- WebP: fallback dla starszych przeglądarek -->
  <source
    type="image/webp"
    srcset="
      /images/hero-400.webp   400w,
      /images/hero-800.webp   800w,
      /images/hero-1200.webp 1200w
    "
    sizes="(max-width: 768px) 100vw, 50vw"
  />
  <!-- JPEG: ostateczny fallback -->
  <img
    src="/images/hero-800.jpg"
    alt="Hero banner"
    width="1200"
    height="630"
    loading="lazy"
    decoding="async"
  />
</picture>

Kiedy wybrać natywny srcset

  • Static export (output: 'export'): next/image domyś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ć”.

Code
<picture>
  <source
    media="(max-width: 767px)"
    srcset="/images/hero-mobile.avif 800w"
    type="image/avif"
  />
  <source
    media="(min-width: 768px)"
    srcset="/images/hero-desktop.avif 1600w"
    type="image/avif"
  />
  <img
    src="/images/hero-desktop.jpg"
    alt="Panel analityczny kampanii Google Ads na ekranie laptopa"
    width="1600"
    height="900"
    fetchpriority="high"
  />
</picture>

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 i Cloudflare Images

Dedykowane do obrazów oferują transformacje w locie: skalowanie, kadrowanie, filtry, konwersję formatów bez generowania wariantów w build time.

Integracja CDN z next/image przez custom loader

Code
// lib/cloudinary-loader.ts
export default function cloudinaryLoader({
  src,
  width,
  quality,
}: {
  src: string
  width: number
  quality?: number
}) {
  const params = [
    `w_${width}`,
    `q_${quality || 75}`,
    'f_auto', // Automatyczny format (WebP/AVIF)
    'c_limit', // Nie powiększaj ponad oryginał
  ]
 
  return `https://res.cloudinary.com/twoje-konto/image/upload/${params.join(',')}/${src}`
}
Code
// next.config.ts
const nextConfig = {
  images: {
    loader: 'custom',
    loaderFile: './lib/cloudinary-loader.ts',
  },
}
Code
// Użycie identyczne jak zwykły next/image
<Image
  src="blog/hero-nextjs.jpg" // Ścieżka w Cloudinary
  alt="Next.js hero"
  width={1200}
  height={630}
  sizes="100vw"
/>
// Wynikowy URL: https://res.cloudinary.com/.../w_640,q_75,f_auto,c_limit/blog/hero-nextjs.jpg

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łoNajlepszy wybórDlaczego
lokalne grafiki marketingowestatyczny import + next/imageautomatyczne wymiary i blur, mało wariantów
CMS z własnym image CDN, np. Sanityloader albo natywne URL-e CDNCMS już umie transformować obrazy
e-commerce z tysiącami zdjęćdedykowany image CDNkoszty, cache i transformacje są poza serwerem Next.js
static export<picture> / srcset albo CDN loaderbrak 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.

next/image vs srcset vs CDN: porównanie podejść

Kryteriumnext/imageNatywny srcsetCDN (Cloudinary/Imgix)
Konwersja formatuAutomatyczna wg konfiguracjiRęcznaAutomatyczna
SkalowanieAutomatyczneRęczne wariantyOn-the-fly
Lazy loadingWbudowaneloading="lazy"loading="lazy"
CLS preventionWymuszony width/heightRęczneRęczne
Blur placeholderAutomatyczny dla części importów statycznychRęczny (LQIP)Zależy od CDN
KosztZa serwer Next.jsZeroOpłata za CDN
Static exportWymaga custom loaderDziałaDziała
KontrolaŚredniaPełnaPełna
SetupMinimalnyPracochłonnyŚredni

Jak wybrać optymalizację obrazów w Next.js?

To może teraz najprostszy sposób podjęcie decyzji.

  1. Masz standardową aplikację Next.js z serwerem albo Vercel? Zacznij od next/image.
  2. Robisz output: 'export' bez zewnętrznego loadera? Wybierz <picture> i srcset.
  3. Masz tysiące obrazów z CMS-a, marketplace albo e-commerce? Wybierz image CDN lub loader pod CDN.
  4. Potrzebujesz różnych kadrów na mobile i desktopie? Użyj <picture>, nawet jeśli reszta obrazów idzie przez next/image.
  5. Masz jeden hero i kilka grafik marketingowych? Statyczne importy + next/image są najprostsze i zwykle wystarczają.
  6. Masz już zoptymalizowane URL-e z Sanity, Cloudinary albo Imgix? Nie optymalizuj ich drugi raz bez powodu.

Najlepsze praktyki optymalizacji obrazów

Obraz LCP: 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".

Code
<Image src={hero} alt="..." preload />
// lub natywnie:
// <img src="..." loading="eager" fetchpriority="high" />

Poprawny alt dla SEO i dostępności

Code
// Źle
<Image src={product.image} alt="zdjęcie" />
<Image src={product.image} alt="" />
 
// Dobrze
<Image src={product.image} alt="Buty do biegania Nike Air Max 90 w kolorze białym" />

Unikaj layout shift przez aspect ratio

Code
/* CSS aspect ratio jako zabezpieczenie */
.image-container {
  aspect-ratio: 16 / 9;
  position: relative;
  overflow: hidden;
}

Mierz realny wpływ obrazów na wydajność

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, fill z 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.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Czy next/image spowalnia build?

Nie. next/image optymalizuje obrazy on-demand (przy pierwszym żądaniu), nie w build time. Wygenerowane warianty są cachowane, więc kolejne żądania tego samego wariantu mogą dostać obraz z cache, dopóki wpis jest dostępny i nie wygasł.

Czy AVIF jest lepszy od WebP?

AVIF często daje lepszą kompresję niż WebP przy podobnej jakości, ale kodowanie jest wolniejsze. W Next.js kolejność w images.formats ma znaczenie: jeśli ustawisz AVIF przed WebP, przeglądarka obsługująca oba formaty dostanie AVIF. Przy dużym ruchu sprawdź koszt pierwszego kodowania i cache, bo zysk transferowy może kosztować więcej CPU.

Jak działa next/image w static export?

Domyślnie nie działa, bo wymaga serwera do optymalizacji. Rozwiązania: użyj custom loadera (Cloudinary, Imgix) albo ustaw unoptimized: true w konfiguracji, co wyłącza optymalizację, ale komponent nadal działa. Dla statycznych eksportów często prostszy jest natywny srcset z picture.

Którym propem oznaczyć obraz LCP w Next.js 16?

Propem preload, jeśli obraz jest jednoznacznym kandydatem na LCP i chcesz rozpocząć jego pobieranie już z head dokumentu. W Next.js 16 priority jest przestarzałe na rzecz preload. Nie łącz preload z loading ani fetchPriority. Jeśli kandydat do LCP zależy od viewportu albo masz kilka możliwych obrazów, częściej bezpieczniejsze będzie loading="eager" albo fetchPriority="high".

Po co podawać prop sizes, skoro mam width i height?

width i height ustalają proporcje i chronią przed CLS, a sizes mówi przeglądarce, jaką szerokość obraz realnie zajmie na różnych viewportach. Przy obrazach responsywnych bez sizes przeglądarka zakłada zwykle 100vw, więc może pobrać wariant większy niż faktycznie potrzebny.

Co zmienia images.qualities w Next.js 16?

qualities to allowlista dozwolonych wartości quality. W Next.js 16 jest wymagana, bo nieograniczone parametry jakości pozwalałyby generować zbyt wiele wariantów tego samego obrazu. Jeśli ustawisz qualities: [50, 75, 90], Next użyje najbliższej dozwolonej wartości, a bezpośrednie żądanie API z niedozwoloną jakością dostanie 400.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Jak osiągnąć 100/100 w Lighthouse Performance w Next.js? Case study

Mamy tu typową stronę usługową zbudowaną w Next.js, wyposażoną w Google Analytics, Google Tag Manager , prosty formularz kontaktowy, galerię zdjęć i animacje. Na początku testy PageSpeed Insights pokazały wyniki: 67/100 na urządzeniach mobilnych i 89/100 na komputerach stacjonarnych.

Maciej Sala

Maciej Sala

Founder StriveLab

Next.js a SEO — kiedy naprawdę daje przewagę nad zwykłym Reactem

Next.js to framework React, który rozwiązuje największy problem klasycznych aplikacji SPA : serwer wysyła gotowy HTML zamiast pustej strony. Dzięki temu Google widzi treść w początkowym HTML i nie musi czekać na JavaScript, żeby odkryć najważniejsze elementy strony. Do tego dostajesz wbudowane API do metadanych, automatyczną optymalizację obrazów i code splitting podział kodu ładowanego osobno dla każdej podstrony , który realnie poprawia Core Web Vitals.

Maciej Sala

Maciej Sala

Founder StriveLab

Core Web Vitals — jak przyspieszyć stronę i poprawić pozycję w Google

Google od wielu lat podkreśla, że wydajność i doświadczenie użytkownika mają znaczenie i Core Web Vitals są częścią sygnałów page experience. Najważniejsze jest jednak to, że wreszcie mierzą coś bardzo konkretnego: czy strona błyskawicznie pokazuje treść, czy odpowiednio reaguje na interakcje i czy nie „skacze" nieprzyjemnie podczas ładowania.

Maciej Sala

Maciej Sala

Founder StriveLab