Your Images Are Killing Your LCP Here's How to Fix It

Published on February 14, 2026

Problem

If you've ever run Lighthouse and seen your LCP score in red there's a good chance the problem is an image.

You optimized your JavaScript. You split your bundles. You added CDN caching. But your hero image is still 2MB and the browser is stuck waiting for it. Your LCP is gone.

And here's the thing most developers don't realize in 70–80% of web apps, the LCP element is an image. Hero banners, product shots, blog covers. That one image decides whether your Core Web Vitals pass or fail.


Bad LCP means lower rankings. Lower rankings means fewer users. Fewer users means your app doesn't grow.


This post is everything I know about fixing that.


What Is LCP and Why Images Break It

LCP waterfall breakdown showing resource load timing in Chrome DevTools
LCP waterfall breakdown showing resource load timing in Chrome DevTools

Chrome DevTools highlighting the LCP element on a Shopify product page
Chrome DevTools highlighting the LCP element on a Shopify product page

LCP badge in Chrome Performance Insights
LCP badge in Chrome Performance Insights

Largest Contentful Paint (LCP) measures how long the largest visible element takes to show up on screen.


In 70–80% of web apps, that element is an image hero banner, product image, featured blog image, or something similar.


Google considers LCP good if it's under 2.5 seconds.


But most developers think LCP is just about download speed. It's not.

LCP isn't just download time. It includes TTFB (Time to First Byte), resource discovery, download time, decoding, and rendering. So even if your image is compressed, if it's loaded through JavaScript or blocked behind heavy scripts, LCP will still be bad.


Step 1: Fix LCP Image Priority

If your above-the-fold image has loading="lazy", you already have a problem.

❌ Don't do this

html
<img src="/hero.jpg" loading="lazy" />

Lazy loading delays LCP. It's meant for images below the fold not for your hero image.

✅ Do this instead

html
<link rel="preload" as="image" href="/hero.jpg" />
<img src="/hero.jpg" fetchpriority="high" alt="Hero" />

This tells the browser to load that image first.

In NextJS, pass the priority prop on the Image component:

jsx
<Image src="/hero.jpg" priority alt="Hero" />;

That one change alone can drop LCP by 300–800ms.


Step 2: Use LQIP (Low Quality Image Placeholder)

LQIP blurred placeholder vs full image
LQIP blurred placeholder vs full image

LQIP = Low Quality Image Placeholder.

Instead of showing a blank space while the image loads, you show a tiny blurred version usually 10–20px wide, or a base64-encoded thumbnail. Then swap it once the full image is ready.


Even if the real image takes 1.8 seconds, the user sees something right away. That changes the feel of the entire page.

Basic HTML version

html
<img
src="data:image/jpeg;base64,/9j/4AAQSk..."
data-src="/real-image.jpg"
class="blur"
/>

NextJS version

jsx
<Image
src="/hero.jpg"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>;
When to skip LQIP
  • Small icons
  • Images under 10kb
  • Below-the-fold content
  • Already compressed thumbnails

No point adding complexity for images that load instantly anyway.


Step 3: Serve Different Images by Screen Size

Your users are on different devices.

Mobile: 375px. Tablet: 768px. Desktop: 1440px+.

Sending a 2000px image to a mobile phone is just wasted bandwidth. Without srcset, a phone downloads the same oversized image meant for a 27-inch monitor. That's bad for performance and bad for users on slow connections.

srcset and sizes

html
<img
src="image-800.jpg"
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1600.jpg 1600w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Product"
/>

The browser picks the smallest image that fits. Less data, faster load, lower LCP.


Step 4: Handle Pixel Density (Retina Screens)

Not all screens render pixels the same way.

  • devicePixelRatio = 1 → standard screen
  • devicePixelRatio = 2 → Retina / HiDPI
  • devicePixelRatio = 3 → flagship phones

If you only serve 1x images on a 2x screen, everything looks blurry.

Density descriptors

html
<img src="logo.jpg" srcset="logo.jpg 1x, logo@2x.jpg 2x" alt="Logo" />

The browser picks the right version based on the screen.

Don't force 2x images on everyone that doubles file size for users who don't need it. Let the browser decide.


Step 5: Orientation-Based Images

Sometimes the same image doesn't work for both portrait and landscape. The crop changes, the composition breaks.

Use <picture> for this:

html
<picture>
<source media="(orientation: portrait)" srcset="portrait.jpg" />
<source media="(orientation: landscape)" srcset="landscape.jpg" />
<img src="landscape.jpg" alt="Hero" />
</picture>

Only use orientation switching when the design actually needs different crops. Don't add it everywhere it's extra work for minimal gain in most cases.


Step 6: Use WebP and AVIF

  • JPEG and PNG are old and heavy.

  • WebP and AVIF give you 25–50% smaller files with the same visual quality.

Fallback structure

html
<picture>
<source srcset="image.avif" type="image/avif" />
<source srcset="image.webp" type="image/webp" />
<img src="image.jpg" alt="Product" />
</picture>

The browser picks the best format it supports. If it can handle AVIF, it uses that. Otherwise WebP. Otherwise JPEG.

CDNs like Cloudinary, ImageKit, and Fastly can auto-convert formats for you. The NextJS Image component does this too.


Step 7: Set Width and Height

If you don't set width and height on your images, the browser doesn't know how much space to reserve. That causes layout shift your CLS score goes up, and the page feels jumpy.

html
<img src="image.jpg" width="800" height="600" alt="Product" />

Or use aspect-ratio in CSS. Either way, tell the browser the dimensions upfront.


Step 8: Don't Use CSS Background for LCP Images

Problem

If your hero image looks like this:

css
.hero {
background-image: url("/hero.jpg");
}

The browser might not prioritize it. CSS backgrounds are treated differently they're discovered later in the rendering pipeline.

Use <img> tags for anything that needs to load fast. Save background-image for decorative stuff.


How This Works in Production

Here's what the workflow looks like in a real project:

1. Find the LCP element

Open Chrome DevTools, run Lighthouse, or use PageSpeed Insights. Find out which element is your LCP it's usually an image.

2. Fix loading priority

Remove loading="lazy" from that image. Add <link rel="preload">. Set fetchpriority="high". Make sure no heavy JS blocks it.

3. Generate image variants

Image variant checklist

For your key images, create multiple versions:

  • Widths: 400w, 800w, 1200w, 1600w
  • Densities: 1x, 2x
  • Formats: WebP, AVIF

Automate this with your build pipeline or CDN.

4. Add LQIP only where it matters

Don't blur every image. Apply LQIP to your hero image and a few above-the-fold images. That's it.

5. Watch real user data

Lab scores are just lab scores. Track real LCP, CLS, and FCP through analytics or RUM tools like Vercel Analytics or Google CrUX.


Mistakes I Keep Seeing

  1. Lazy loading the hero image 2. Loading images after a large JS bundle finishes 3. Sending desktop-size images to all devices 4. No width/height causing layout shift 5. Ignoring pixel density 6. Adding LQIP to every single image 7. Using background-image for the main content image

If your LCP image gets injected via JavaScript after hydration, you're adding delay for no reason.

When to Stop Optimizing

Don't go overboard
  • Don't generate 12 variants for a 24px icon
  • Don't use orientation switching for simple layouts
  • Don't add LQIP for 5kb images
  • Don't force AVIF if your users are on browsers that don't support it

The goal is better performance, not more complexity.

Wrapping Up

Image optimization isn't just about compression.


It's about sending the right size, in the right format, at the right time, to the right device.


If your LCP image loads fast, the whole app feels fast. And when the app feels fast, people stay.


Thank you for reading. If you found this useful, share it with someone who needs it.

linkedin | github | twitter