Next.js image optimization: the complete Core Web Vitals checklist
A practical Next.js image optimization checklist for Core Web Vitals: WebP/AVIF, responsive srcset, priority on LCP, width/height for CLS, lazy loading. Images are the most common performance killer.

In short: images are the most common reason a site fails Core Web Vitals — more often than JavaScript. A poor mobile LCP is almost always an oversized image served in the wrong format, without dimensions. The checklist below fixes the cases that matter.
I've seen this many times in audits: a site with tens of seconds of mobile LCP, where the culprit was a single 8MB image. Case study here. Other times JavaScript was the problem — but whenever it wasn't JS, it was the images.
1. Modern format: WebP or AVIF
Large PNGs and JPEGs are history. WebP is 25-35% smaller at the same quality; AVIF even more. In Next.js, the next/image component serves them automatically if you have optimization enabled. If you use unoptimized: true (hosting without runtime optimization), pre-generate the WebP variants at build and serve them via <picture>.
The rule: no multi-megabyte raster image in production. A full-screen hero needs a few dozen KB in WebP, not 2-8MB in PNG.
2. Responsive sizes (srcset + sizes)
A phone doesn't need the 1920px image. Serve multiple variants and let the browser choose:
<picture>
<source media="(max-width: 640px)" srcset="/img/mobile.webp" type="image/webp" />
<source media="(max-width: 960px)" srcset="/img/tablet.webp" type="image/webp" />
<source srcset="/img/desktop.webp" type="image/webp" />
<img src="/img/fallback.jpg" alt="..." />
</picture>
With next/image, sizes does the same automatically. Without it, mobile downloads the desktop image and saturates bandwidth — exactly what destroys LCP.
3. priority on the LCP image
The above-the-fold image (hero, article cover) must load immediately, not lazily. In next/image you use priority; with a raw <img>, loading="eager" + fetchpriority="high". The rest stay loading="lazy".
The classic mistake: the hero is lazy-loaded like everything else → the browser defers it → high LCP. Explicitly mark the main image as priority.
4. width and height for CLS
Without explicit dimensions, the browser doesn't know how much space to reserve for the image, so content jumps as it loads — Cumulative Layout Shift. Always set width and height (or aspect-ratio in CSS). next/image enforces them; with a raw <img> you add them manually.
5. Lazy loading below the fold
Everything below the first viewport loads lazily: loading="lazy". This way bandwidth goes first to what the user sees, not to the image at the bottom of the page. next/image is lazy by default (except when you set priority).
6. Compression and decoding
- Compress at quality 75-85 — the visual difference is negligible, the size gain is real.
decoding="async"on images so they don't block rendering.- Serve via CDN with long cache headers — images don't change often.
How to measure
Don't guess — measure on real mobile in PageSpeed Insights. In the waterfall you immediately see the resource with the largest download time. If it's an image, you found the culprit. Check LCP (under 2.5s), CLS (under 0.1) and the total image weight on the page. Contradictory results — good FCP but absurd LCP — are almost always a heavy blocking image.
Summary
Images are the number-one lever for CWV. Modern format (WebP/AVIF), responsive sizes, priority on LCP, width/height for CLS, lazy below the fold. Five rules that fix most of the performance problems we see.
We do this on every project — see how we took a site from 74 to 97 on mobile. For continuous Core Web Vitals monitoring after launch, it's part of the maintenance service.