Image Optimization Is the Single Biggest LCP Win
Photo: Unsplash
If you only have time to optimize one thing for perceived speed, optimize your images. On the majority of web pages, the Largest Contentful Paint element — the biggest thing visible in the initial viewport, the one Core Web Vital that correlates most directly with "this page feels slow" — is an image. A hero banner, a product photo, an article's cover.
That's a gift, because it means one element decides your score. Fix the loading of that single image and you've moved the metric that matters most. (LCP is one of three Core Web Vitals — see the complete guide to LCP, INP, and CLS for the full picture.) Here's the playbook, roughly in order of impact.
Serve the right format
JPEG and PNG are decades old. Modern formats compress dramatically better at the same
visual quality. AVIF typically beats JPEG by
50% or more on file size; WebP is a safe, near-universal middle ground. The
<picture> element lets the browser pick the first format it supports and fall back
gracefully:
<picture>
<source srcset="hero.avif" type="image/avif" />
<source srcset="hero.webp" type="image/webp" />
<img src="hero.jpg" alt="Mountain at sunrise" width="1600" height="900" />
</picture>The browser downloads exactly one of these. Old browsers get the JPEG; everyone else gets something far smaller. This change alone routinely cuts hero image weight in half with no visible quality loss.
Stop shipping desktop images to phones
A 1600px-wide hero on a 390px phone screen is wasted bytes — the device downloads four
times the pixels it can display. Responsive images via srcset and sizes let the
browser choose an appropriately sized file for the viewport and pixel density:
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
width="1600" height="900"
alt="Mountain at sunrise"
/>The MDN guide to responsive images
walks through w descriptors and the sizes attribute in detail. The mental model:
srcset lists the files you have, sizes tells the browser how big the image will render
so it can pick before layout is even complete.
Don't make the LCP image wait
The biggest LCP mistakes aren't about file size — they're about timing. Two anti-patterns
dominate. First, lazy-loading the hero. loading="lazy" is great for below-the-fold
images, but on your LCP element it actively delays the metric, because the browser defers
the request. Never lazy-load the LCP image. Mark it eager and high-priority instead:
<img src="hero.avif" fetchpriority="high" alt="..." width="1600" height="900" />Second, hiding the image behind JavaScript. If your hero is a CSS background-image
injected by a component, or an <img> whose src is set after hydration, the browser's
preload scanner can't discover it early. Put the real <img> in the server-rendered HTML
so the request starts during the initial parse. For critical images you can go further with
a preload hint in the <head>.
The fastest image request is the one the browser starts before it finishes reading the
page. That only happens when the image is a plain <img> in your initial HTML with a
discoverable src. JavaScript-driven images always start late.
Always reserve the space
Images that load without declared dimensions cause layout shift:
content jumps as the image pops in, hurting your CLS score and annoying users mid-read.
The fix is trivial and you've seen it in every example above — always set width and
height attributes (or an aspect-ratio in CSS). The browser reserves the correct box
before the image arrives, so nothing jumps:
img {
height: auto; /* keep aspect ratio */
aspect-ratio: 16 / 9;
}This costs nothing and prevents one of the most jarring perceived-quality problems on the web. There is no reason to ship an image without dimensions.
Compress, then verify
Even in a modern format, images are often saved at quality settings far higher than anyone
can perceive. Run them through a compressor — sharp in a build step,
Squoosh for one-offs, or a tool from your
framework. A quick Node example using sharp:
import sharp from "sharp";
await sharp("hero.png")
.resize(1600)
.avif({ quality: 55 })
.toFile("hero.avif");Then verify in the field. LCP is a real-user metric, so measure it with the web-vitals library and watch your p75, not your laptop. The numbers will tell you whether the hero is genuinely the bottleneck or whether something upstream — a slow server response, render-blocking CSS — is delaying it.
Takeaways
- The LCP element is usually an image. Fixing it moves the metric users feel most.
- Serve AVIF/WebP with
<picture>fallbacks; it routinely halves file size with no visible loss. - Use
srcset/sizesso phones don't download desktop-sized images. - Never lazy-load the LCP image, and keep it a real
<img>in server HTML withfetchpriority="high"so the request starts early. - Always set width/height to prevent layout shift, compress aggressively, and verify p75 LCP in the field.

