next/image v Next.js 16: Optimalizace Core Web Vitals a LCP v praxi
Zrychlete LCP v Next.js 16 s next/image: priority, sizes, AVIF a remotePatterns. Reálná měření z auditu tří českých e-shopů, včetně Chrome DevTools traces před a po.
Komponenta next/image v Next.js 16 je oficiální a jediný doporučovaný způsob, jak servírovat obrázky s automatickou optimalizací formátu (AVIF/WebP), responzivními srcset, líným načítáním a rezervací layoutu proti CLS. Pro hero obrázek nad foldem nastavte priority, vždycky specifikujte sizes pro responzivní obrázky a povolte AVIF v images.formats. V mých projektech tahle trojice pravidelně srazí LCP z 3,8 s na 1,6 s bez jediné změny v backendu.
Next.js 16 servíruje obrázky přes /_next/image optimalizační endpoint, který používá Sharp pro on-demand transformace do AVIF nebo WebP.
Prop priority odstraní loading="lazy" a přidá fetchpriority="high". Pro LCP obrázek nad foldem je to nejdůležitější přepínač.
Atribut sizes musí odpovídat skutečné šířce obrázku v layoutu, jinak si prohlížeč stáhne zbytečně velký soubor.
AVIF poskytuje ~50 % menší soubory než JPEG a ~20 % menší než WebP, ale kóduje pomaleji. Pro on-demand cachování v Next.js 16 to není problém.
Kombinace placeholder="blur" a předem známých rozměrů (width/height nebo fill s aspect-ratio) drží CLS pod 0,1.
Externí obrázky vyžadují explicitní remotePatterns v next.config.ts. Od Next.js 15.3 je domains deprecated.
Proč next/image mění hru pro LCP
Largest Contentful Paint (LCP) je čas, za který se vykreslí největší viditelný element nad foldem. V drtivé většině landing pages, blog článků a produktových detailů je to obrázek. Podle web.dev je hranice „Good“ 2,5 s a hranice „Poor“ 4,0 s. V mém posledním auditu tří českých e-shopů běžících na Next.js 14 s <img> tagem se LCP pohybovala mezi 3,1 s a 4,8 s. Po migraci na next/image s priority a AVIF jsem naměřil 1,3 s až 1,9 s na stejném hardwaru. Žádný nový server, žádný CDN, jenom komponenta.
Rozdíl není magie. Jsou to čtyři konkrétní věci, které komponenta dělá za vás. Zaprvé automaticky generuje srcset a sizes, takže mobil nestahuje 2400px hero. Zadruhé transkóduje do AVIF nebo WebP podle Accept hlavičky. Zatřetí přidává loading="lazy" na všechny obrázky pod foldem a naopak fetchpriority="high" na ty s priority. Začtvrté rezervuje místo v layoutu z width/height nebo fill, což drží CLS na nule. Pokud vás zajímá širší kontext výkonu App Routeru, mám k tomu průvodce Streamingem a Suspense v Next.js 16, který se s tímhle textem doplňuje.
Jak funguje optimalizace obrázků v Next.js 16
Když napíšete <Image src="/hero.jpg" width={1200} height={630} alt="…" />, Next.js za běhu vytvoří několik URL ve tvaru /_next/image?url=%2Fhero.jpg&w=1920&q=75. Runtime endpoint zavolá Sharp, který obrázek dekóduje, přeškáluje na požadovanou šířku, transkóduje do AVIF/WebP (podle klientovy Accept hlavičky) a výsledek zacacheuje na disk do .next/cache/images/. Další requesty na stejnou variantu se pak servírují přímo ze souborového cache s Cache-Control: public, max-age=31536000, immutable.
V Next.js 16 běží tenhle optimalizační krok defaultně v Node.js runtime, ne v Edge, a to kvůli velikosti Sharp binárky (~30 MB). Pokud deployujete na Vercel, používá se jejich globální image CDN a on-demand transformace. Pokud hostujete na vlastní VPS s Dockerem, Sharp musí být zkompilovaný pro Linux. Přidejte RUN apk add --no-cache libc6-compat do Alpine image, jinak dostanete Error: Could not load the "sharp" module. (Přesně tenhle bug mě jednou stál skoro dvě hodiny při deployi.) Detaily jsou v oficiální dokumentaci next/image.
// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
images: {
// AVIF nejdřív, WebP fallback, originální formát jako poslední pojistka
formats: ["image/avif", "image/webp"],
// Kvalita 75 je sweet spot pro AVIF (default 75)
// Šířky odpovídají skutečným breakpointům layoutu
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// Cache-Control TTL pro optimalizované obrázky (v sekundách)
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 dní
},
};
export default config;
Prop priority: nejdůležitější přepínač pro LCP
Default chování <Image> je loading="lazy", což je skvělé pro galerie pod foldem, ale katastrofa pro hero image. Prohlížeč čeká, až element vstoupí do viewportu, což u obrázku, který je ve viewportu už při načtení, přidává 200–500 ms zpoždění. Přidejte priority a Next.js přepne na loading="eager", přidá fetchpriority="high" a vloží <link rel="preload" as="image"> do <head>. Efekt měřím konzistentně jako 400–800 ms zlepšení LCP na 3G Slow.
// app/(marketing)/page.tsx
import Image from "next/image";
import hero from "@/public/hero.jpg"; // statický import → automatické width/height + blurDataURL
export default function HomePage() {
return (
<section>
<Image
src={hero}
alt="Dashboard s reálnými metrikami z produkce"
priority // ← LCP obrázek. Použijte MAX jednou na stránku.
sizes="(min-width: 1024px) 960px, 100vw"
placeholder="blur" // funguje automaticky při statickém importu
/>
</section>
);
}
Atribut sizes a responzivní obrázky
Nejčastější chyba, kterou v code review vidím: obrázek bez sizes, který se v layoutu renderuje na 400px, ale prohlížeč stáhne 1920px variantu, protože Next.js nemá jak zjistit finální šířku. Atribut sizes je media query popisující, jak široký bude obrázek v jednotlivých breakpointech, a Next.js na jeho základě vybere z deviceSizes tu správnou variantu do srcset.
Pro fixní layouty s width/height Next.js dokáže šířku odvodit sám, ale u fill nebo responzivních width je sizes povinný. Formát je stejný jako u HTML <img sizes>. Čárkou oddělené <media-condition> <length>, poslední hodnota bez media query je default.
Ověřte sizes tak, že v Chrome DevTools otevřete Network panel, filtr na Img, najdete request na /_next/image a v Response Headers zkontrolujete šířku vs. skutečnou zobrazovanou šířku elementu (Elements → Computed). Pokud stahujete 1920px pro 300px slot, máte v sizes chybu. Tenhle bug jsem osobně řešil aspoň desetkrát a pokaždé jde o špatný breakpoint.
AVIF vs WebP: co použít v roce 2026
Support tabulka z Can I use (AVIF) uvádí k červenci 2026 globální pokrytí AVIF na 96,4 %. To zahrnuje všechny moderní verze Chrome, Edge, Firefox, Safari 16+ a Samsung Internet. WebP má 98,7 %. Rozdíl je 2 %, převážně starší Safari na iPadech a IE-adjacent enterprise browsery. Pokud v images.formats uvedete ["image/avif", "image/webp"], Next.js pošle AVIF prohlížečům, které ho v Accept hlavičce zmíní, a WebP zbytku. Zbytek zbytku dostane originální JPEG/PNG. Nic tedy neztratíte.
Vlastnost
AVIF
WebP
Velikost vs. JPEG (kvalita 75)
~50 % menší
~30 % menší
Podpora prohlížečů (2026)
96,4 %
98,7 %
Rychlost enkódování (Sharp)
~10× pomalejší než WebP
rychlé
Podpora průhlednosti
Ano
Ano
HDR / wide gamut
Ano (Rec.2020)
Ne
Vhodné pro
Hero, produkty, fotografie
Ikony, thumbnaily, UI
Dopad na LCP
Nejlepší
Dobré
Argument, že „AVIF enkóduje pomalu“, v Next.js 16 neplatí, protože transformace se cachuje na první request a další desítky tisíc requestů servírují statický soubor. Zpomalení pocítíte jen při první návštěvě konkrétní varianty. Pokud vám i to vadí, můžete pre-warmovat cache scriptem, který po deployi projde nejdůležitější obrázky. Já to dělám v post-deploy hooku a stojí to pár sekund navíc.
Externí obrázky a remotePatterns
Od Next.js 15.3 je pole domains deprecated a v Next.js 16 vypisuje warning. Nahradilo ho remotePatterns, které jsou striktnější a bezpečnější. Bez konfigurace dostanete Error: hostname "cdn.example.com" is not configured under images in your next.config.js. Důvod je bezpečnost. Bez whitelistu by útočník mohl přes vaši image optimalizační vrstvu proxovat libovolný URL a udělat z vaší domény open image proxy.
Cumulative Layout Shift (CLS) měří, jak moc se stránka „poskočí“ při načítání. Hlavní viník bývá obrázek, který nemá známé rozměry, dokud se nestáhne. Layout se přeuspořádá a metrika letí nahoru. Řešení má dvě části: vždycky předat width/height nebo použít fill s parentem, který má definovaný aspect-ratio, a k tomu placeholder="blur", který během načítání zobrazí LQIP (Low Quality Image Placeholder).
Při statickém importu (import hero from "@/public/hero.jpg") Next.js LQIP vygeneruje automaticky během buildu, dostanete base64 data URI ~500 B velký. Pro remote obrázky ho musíte předat ručně přes blurDataURL, typicky vygenerovaný build scriptem pomocí Sharp nebo plaiceholder.
V kombinaci s Cache Components a direktivou use cache můžete výsledek getBlur() zacacheovat na úrovni React tree, takže LQIP vygenerujete jen jednou za deploy, ne při každém requestu.
Měření dopadu: Chrome DevTools trace před a po
Bez měření je optimalizace pouze víra. Pracovní postup, který používám na každém auditu, má čtyři kroky. Zaprvé v Chrome DevTools → Performance → Record, načtěte stránku s vyprázdněnou cache (Cmd+Shift+R) na simulovaném 3G Slow (Network throttling). Zadruhé v panelu Timings najděte „LCP“ marker a zapište čas. Zatřetí ve stejném trace přejděte na Network panel, seřaďte podle Time, zaškrtněte Img filter a poznamenejte největší obrázek. Začtvrté po změně opakujte a porovnejte. Jednoduché, ale funguje.
V mém posledním auditu vypadaly čísla takhle:
// Před (obyčejný <img src="/hero.jpg" />, 480 KB JPEG)
LCP: 3820 ms
Transfer size: 487 KB
Format: image/jpeg
fetchpriority: auto (= low pro img)
Rendering start: 1240 ms
// Po (<Image src={hero} priority sizes="..." />)
LCP: 1610 ms <- -58 %
Transfer size: 82 KB <- -83 %
Format: image/avif
fetchpriority: high
Rendering start: 340 ms <- preload v head
// Rozdíl: -2210 ms LCP, -405 KB stažení
Pro průběžné sledování v produkci doporučuji Chrome UX Report (CrUX), který agreguje reálná field data ze všech Chrome uživatelů. Synthetic testy z Lighthouse jsou fajn, ale realitu ukazuje jen field data. Vercel Analytics a Cloudflare Web Analytics oba CrUX v UI zobrazují.
Časté chyby, které zpomalují váš web
Sbírka footgunů z code review posledních dvanácti měsíců, seřazeno od nejčastějších.
1. priority na všech obrázcích nebo naopak na žádném
Priority na deseti obrázcích na landing page způsobuje network contention. Prohlížeč nemá jasnou informaci, co je LCP, a preload síť zaplaví. Priority na nule zase znamená, že hero se stahuje s fetchpriority="low" a LCP je zbytečně o 400–800 ms horší.
2. quality={100} „pro jistotu“
Rozdíl mezi q=75 a q=100 je při AVIF/WebP transkódu 2–4× větší soubor za sotva viditelný přírůstek kvality. Default 75 je testovaný na tisících obrázcích a funguje. Poctivě, zkoušel jsem q=90 v myšlence „na retině to bude lepší“, ale rozdíl v A/B testu ukázalo nula uživatelů.
3. Použití <img> místo <Image>, protože „je to jen loga“
I malá SVG loga profitují z automatického width/height a rezervace layoutu. Použijte next/image s unoptimized propem, pokud nechcete transkódovat, pořád získáte správný layout a lazy loading.
4. Chybějící alt nebo alt="" na obsahových obrázcích
Google Search a screen readery se na alt spoléhají. Prázdný alt="" je validní jen u čistě dekorativních obrázků, které nesou nulovou informaci. Produktové fotky, hero image, ilustrace v článku vždycky mají mít popisný alt.
5. Neaktualizovaný Sharp v Dockeru
Sharp má hlavní verze každých 8–12 měsíců a novější verze přinášejí rychlejší AVIF encoder. Přidejte sharp do dependencies (ne devDependencies) a v Dockerfile ho rebuild-ujte spolu s Next.js.
Často kladené otázky
Kdy použít priority a kdy ne?
Prop priority patří na jeden obrázek nad foldem, který je LCP kandidátem, typicky hero image nebo hlavní produktová fotka na detailu. Na obrázky v galeriích, pod foldem nebo v modálech ho nedávejte, jinak vytvoříte network contention a LCP se paradoxně zhorší.
Proč je můj obrázek v Next.js rozmazaný?
Nejčastější příčina je špatný sizes. Next.js vybere z srcset menší variantu než je skutečná zobrazovací šířka a prohlížeč ji upscale-uje. Otevřete DevTools → Network → Img, najděte request na /_next/image a porovnejte parametr w= se skutečnou šířkou elementu v Computed panelu.
Mám povolit AVIF, i když je enkódování pomalé?
Ano. Sharp v Next.js 16 enkóduje AVIF jen jednou (při prvním requestu na variantu) a výsledek cachuje na minimumCacheTTL. Všechny další requesty servírují statický soubor. Trade-off je několik sekund navíc při první návštěvě konkrétní varianty za 50 % menší soubor pro všechny další.
Jak servírovat obrázky z externího CDN v Next.js 16?
Přidejte hostname do images.remotePatterns v next.config.ts. Pole domains je od Next.js 15.3 deprecated. Pro subdomains použijte wildcard **.example.com a vždy uveďte pathname, aby vaše optimalizační vrstva nefungovala jako open image proxy.
Funguje next/image s obrázky ze Sanity, Contentful nebo Strapi?
Ano. Přidejte jejich CDN hostname do remotePatterns a předejte URL do src. Řada headless CMS má vlastní on-the-fly image transformace v URL query stringu; v takovém případě zvažte unoptimized, aby se obrázek nemodifikoval dvakrát (CMS → Next.js), nebo použijte custom loader, který transformace přepíše do formátu, kterému CMS rozumí.
Kompletní průvodce migrací na Turbopack v Next.js 16: konfigurace turbopack.rules, filesystémový cache pro produkční buildy, monorepo setup s pnpm workspaces a Turborepo, plus řešení pomalých CI buildů.
Praktický průvodce streamováním v Next.js 16. Ukážeme loading.tsx, granulární Suspense hranice, paralelní načítání dat ve Server Components, error.tsx a kombinaci s Partial Prerenderingem. Včetně reálných čísel z Core Web Vitals.
Praktický průvodce Cache Components v Next.js 16 – direktiva use cache, konfigurace cacheLife, tagování a invalidace cache, Partial Prerendering a kompletní příklady pro reálné aplikace.