← Back to Blog
Performance Core Web Vitals 10 min read

How to Fix LCP Issues Caused by Images: A Developer's Guide

Your LCP element is probably an image. Here's how to get it into the green.

What is LCP and Why Does It Matter?

Largest Contentful Paint (LCP) measures how long it takes for the largest visible element on a page to fully render. It's one of Google's three Core Web Vitals and directly affects your search rankings.

Google's thresholds for LCP:

  • Good - under 2.5 seconds (green)
  • Needs Improvement - 2.5 to 4.0 seconds (orange)
  • Poor - over 4.0 seconds (red)

On over 70% of web pages, the LCP element is an image, typically the hero banner, a product photo, or a featured article image. If your LCP score is in the red or orange zone, the fix almost always involves optimizing that image.

Step 1: Identify Your LCP Element

Before you fix anything, you need to know which element is causing the problem.

Using Chrome DevTools

  1. Open your page in Chrome
  2. Press F12 to open DevTools
  3. Go to the Performance tab
  4. Click the reload button (circular arrow) to record a page load
  5. Look for the "LCP" marker in the timeline
  6. Click it to see which element was identified as the LCP

Using Lighthouse

  1. Open DevTools → Lighthouse tab
  2. Select "Performance" and click "Analyze page load"
  3. Scroll to the "Largest Contentful Paint element" section
  4. It will show the exact element and its load time breakdown

Using PageSpeed Insights

Visit pagespeed.web.dev, enter your URL, and look for the LCP diagnostic. It shows both lab data (simulated) and field data (real users via CrUX).

Step 2: Understand the LCP Timeline

An image-based LCP consists of four sub-parts. To fix LCP, you need to know which part is slow:

The Four Phases of Image LCP

  1. Time to First Byte (TTFB) - how long until the server starts sending the HTML document
  2. Resource Load Delay - time between TTFB and when the browser starts downloading the image
  3. Resource Load Duration - how long it takes to download the image file
  4. Element Render Delay - time between download complete and the image appearing on screen

Most LCP issues fall into phases 2 and 3: the browser discovers the image too late, and/or the image file is too large.

Step 3: Preload Your LCP Image

The biggest quick win for LCP is telling the browser about your hero image as early as possible. By default, the browser discovers images only after it finishes parsing the HTML and CSS. Preloading jumps the queue.

Add a Preload Link in Your <head>

<head>
  <!-- Preload the LCP image with high priority -->
  <link rel="preload" as="image" href="/images/hero.avif"
        type="image/avif"
        fetchpriority="high">
</head>

For Responsive Images with srcset

<link rel="preload" as="image"
      href="/images/hero-1600.avif"
      imagesrcset="/images/hero-800.avif 800w,
                   /images/hero-1200.avif 1200w,
                   /images/hero-1600.avif 1600w"
      imagesizes="100vw"
      fetchpriority="high">

Add fetchpriority to the img Tag

<img src="/images/hero.avif"
     alt="Hero banner"
     fetchpriority="high"
     width="1600" height="900"
     decoding="async">

The fetchpriority="high" attribute tells the browser to prioritize this image over other resources like non-critical scripts and below-the-fold images.

What NOT to Do

  • Never lazy load your LCP image - loading="lazy" on the LCP element delays it, making your score worse
  • Don't load it via CSS background-image - the browser can't discover CSS backgrounds until after the stylesheet is parsed
  • Don't load it via JavaScript - JS-loaded images are discovered last

Step 4: Compress and Optimize the Image File

A large image file directly increases the "Resource Load Duration" phase. The solution is aggressive compression.

Target File Sizes for LCP Images

  • Hero banner (full-width) - aim for under 150KB
  • Featured article image - aim for under 80KB
  • Product hero image - aim for under 100KB
  • Blog post header - aim for under 60KB

How to Achieve These Sizes

  1. Convert to AVIF or WebP - use ZeroPNG's Image Converter to convert from JPEG/PNG to a modern format
  2. Compress aggressively - use the Image Compressor at quality 70-80. For LCP images, the tradeoff leans toward speed over perfection
  3. Resize to actual display dimensions - if your hero displays at 1440px wide, don't serve a 4000px image. Max 2x display width (2880px) for Retina
  4. Strip EXIF metadata - hidden metadata adds 10-100KB of dead weight. Use the EXIF Remover Cropper Watermark

Step 5: Serve the Right Size with srcset

A single image can't serve all screen sizes efficiently. Use srcset to let the browser choose the optimal size:

<picture>
  <source
    srcset="/images/hero-640.avif 640w,
           /images/hero-960.avif 960w,
           /images/hero-1280.avif 1280w,
           /images/hero-1920.avif 1920w"
    sizes="100vw"
    type="image/avif">
  <source
    srcset="/images/hero-640.webp 640w,
           /images/hero-960.webp 960w,
           /images/hero-1280.webp 1280w,
           /images/hero-1920.webp 1920w"
    sizes="100vw"
    type="image/webp">
  <img src="/images/hero-1280.jpg"
       alt="Hero banner"
       width="1920" height="1080"
       fetchpriority="high"
       decoding="async">
</picture>

A mobile user on a 375px screen downloads the 640w variant (~30KB) instead of the 1920w variant (~150KB). That's a 5x reduction in download size for mobile users, where slow connections hit LCP the hardest.

Step 6: Eliminate Render-Blocking Resources Before the Image

Even if your image is preloaded and compressed, it can't render until the browser builds the render tree. Render-blocking CSS and synchronous scripts delay everything.

CSS Optimizations

  • Inline critical CSS - embed the CSS needed for above-the-fold content directly in your <head>
  • Defer non-critical CSS - load stylesheets for below-the-fold content asynchronously
  • Avoid @import - nested CSS imports create sequential waterfalls

JavaScript Optimizations

  • Add defer or async to all <script> tags that aren't critical for initial render
  • Move scripts to the end of <body> - ensures HTML is parsed first
  • Don't set the LCP image src via JavaScript - the browser's preload scanner can't see JS-assigned URLs

Font Optimizations

  • Use font-display: swap - prevents text from being invisible while fonts load
  • Preload critical fonts - <link rel="preload" as="font" ...>
  • Self-host fonts - eliminates the DNS lookup + connection to Google Fonts

Step 7: Use a CDN for Image Delivery

A CDN (Content Delivery Network) serves images from edge servers geographically close to your users, reducing latency:

  • Cloudflare - free tier includes CDN + automatic WebP/AVIF conversion via Polish
  • Cloudinary - automatic format selection, resizing, and optimization via URL parameters
  • imgix - real-time image processing CDN with aggressive caching
  • Vercel / Netlify - built-in image optimization for Next.js and other frameworks

Even without fancy image CDNs, simply putting your site behind Cloudflare's free CDN reduces TTFB and image download times by 30-60% for geographically distant users.

Step 8: Always Set Width and Height

Missing width and height attributes cause layout shifts (CLS) that can also delay LCP. When the browser doesn't know the image dimensions in advance, it can't reserve space, causing the page to jump when the image loads.

<!-- Always include width and height -->
<img src="hero.avif" width="1600" height="900"
     alt="Hero" fetchpriority="high">

<!-- For CSS-responsive images, add this CSS: -->
<style>
img {
  max-width: 100%;
  height: auto;
}
</style>

The browser uses the width/height ratio to calculate an aspect-ratio box before the image loads, eliminating layout shifts entirely.

Debugging Checklist

When your LCP is in the red, run through this checklist in order:

  1. Identify the LCP element - is it an image? A text block? A video poster?
  2. Check if it's preloaded - is there a <link rel="preload"> for this resource?
  3. Check fetchpriority - does the <img> have fetchpriority="high"?
  4. Check for lazy loading - is loading="lazy" accidentally on the LCP image? Remove it
  5. Check file size - is the image over 200KB? Compress it
  6. Check format - is it JPEG/PNG? Convert to AVIF/WebP
  7. Check dimensions - are you serving a 4000px image displayed at 800px? Resize it
  8. Check for CSS background-image - switch to an <img> tag for discoverability
  9. Check render-blocking resources - are large CSS/JS files loading before the image?
  10. Check for redirect chains - is the image URL redirecting (HTTP → HTTPS, www → non-www)?

Real-World Example: Fixing a 6.2s LCP

Here's a real scenario from a portfolio site with a full-width hero image:

Before (LCP: 6.2 seconds)

  • Hero image: 4032x3024 JPEG, 4.8MB
  • No preload, loaded via CSS background-image
  • Three render-blocking stylesheets (320KB total)
  • Two synchronous scripts in <head>
  • No CDN (single origin server in US East)

After (LCP: 1.8 seconds)

  • Hero image: 1920x1080 AVIF, 89KB (compressed with ZeroPNG)
  • <link rel="preload"> in <head> with fetchpriority="high"
  • Switched from background-image to <img> tag
  • Critical CSS inlined, non-critical deferred
  • Scripts deferred with async
  • Cloudflare CDN enabled (free tier)
  • Added srcset for 640w, 960w, 1280w, 1920w variants

Result: LCP improved from 6.2s to 1.8s, well within Google's "Good" threshold of 2.5s.

Optimize Your LCP Image Now

Use ZeroPNG to compress, resize, and convert your hero images for maximum LCP performance. Free, private, browser-based.

Compress Images Now