Here's something that keeps annoying me. People spend weeks debugging their LCP score, reading Google's documentation three times over, running Lighthouse twelve times in a row hoping for a different number. And most of the time? It's one image. Their hero image is 2.4MB, served as a PNG, loaded without any priority hint. That's it. That's the whole mystery.
I'm not saying LCP is always simple. Sometimes it's messy. But 7 out of 10 times, fixing Largest Contentful Paint comes down to a handful of very specific things that take maybe an afternoon to sort out.
What LCP Actually Measures (Quick Version)
LCP tracks how long it takes for the biggest visible thing on your page to finish rendering. "Biggest visible thing" usually means your hero image, a large heading, or a big background image. Google picks the element that takes up the most pixels in the viewport.
LCP Thresholds (What Google Actually Uses)
Good: Under 2.5 seconds
Needs Improvement: 2.5 to 4 seconds
Poor: Over 4 seconds
Google uses the 75th percentile from Chrome User Experience Report (CrUX) field data. Your Lighthouse lab score does NOT determine rankings. Real user data does.
Quick tip before we go further: run your site through VitalsFixer or PageSpeed Insights and look at the filmstrip. Find the frame where your biggest element appears. That's your LCP moment. Everything we're about to do is about making that moment happen sooner.
Step 1: Find Your LCP Element
You can't fix something if you don't know what it is. Open PageSpeed Insights, run your URL, and scroll down to the "Largest Contentful Paint element" diagnostic. It'll tell you exactly which element is your LCP.
For most websites it's one of these:
- Hero image (the big banner at the top)
- H1 heading (if there's no hero image)
- Background image set via CSS
- Video poster frame
If it's an image, keep reading. If it's text, skip to the server response section because your issue is probably TTFB or render-blocking CSS.
Step 2: Fix Your Hero Image (The Big One)
Here's where most LCP problems live. Your hero image is too big, wrong format, loaded wrong, or all three at once.
Convert to WebP (or AVIF)
If your hero image is a JPEG or PNG, you're leaving performance on the table. WebP gives you roughly 25-35% smaller files at the same visual quality. AVIF pushes that to 40-50% but browser support still isn't universal.
Easiest way to convert: open Squoosh.app, drop your image in, pick WebP, set quality to 75-80, and compare. If it looks the same to your eyes, ship it.
Real Numbers From a Real Site
A client's hero image was a 1.8MB JPEG at 2400x1600. After converting to WebP at quality 78 and resizing to 1200x800 (which matched their actual display size), it was 127KB. LCP went from 4.1s to 1.9s. Same image. Same quality. Just, you know, not insane file size anymore.
Add fetchpriority="high"
This is probably the single most underused attribute in web development. Adding
fetchpriority="high" to your hero image tells the browser "download this before anything else."
Without it the browser treats your hero the same as every other image on the page.
<img src="hero.webp"
alt="Your hero image description"
width="1200" height="600"
fetchpriority="high"
decoding="async">
That one attribute can cut 200-500ms off your LCP. And you'll see it immediately in Lighthouse.
Do NOT Lazy Load Your Hero Image
This is the mistake I see every single week. Some plugin or framework adds loading="lazy" to
every image on the page, including the hero. That means the browser won't even start downloading your LCP
element until it scrolls into view. Which is immediately, because it's at the top of the page. But the
browser doesn't know that yet.
The Rule
Above-the-fold images: loading="eager" or just don't set the attribute at all.
Below-the-fold images: loading="lazy"
If you're using WordPress and WP Rocket or a similar plugin, check the settings. Most of them have an "exclude above-the-fold images" option. Turn it on.
Preload the Hero Image
For maximum speed, add a preload hint in your <head>. This makes the browser start
downloading the image before it even parses the HTML where the image tag lives:
<link rel="preload" as="image" href="/images/hero.webp"
type="image/webp" fetchpriority="high">
This is especially useful when your hero image URL is buried deep in the HTML or loaded via CSS background-image (where the browser can't discover it until it parses the CSS).
Size Your Images Properly
If your image displays at 600px wide on screen, don't serve a 3000px wide original. Use srcset
to serve different sizes for different screen widths:
<img src="hero-800.webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, 800px"
alt="Hero description"
width="800" height="400"
fetchpriority="high"
decoding="async">
Mobile users on 4G connections will get the 400px version instead of the 1200px version. That alone could save 60-70% of bytes on mobile.
Step 3: Fix Your Server Response Time (TTFB)
Time to First Byte is how long it takes your server to start sending the HTML back to the browser after a request. If your TTFB is over 800ms, no amount of image optimization will save you because the browser can't even start loading your page for almost a full second.
Common TTFB Problems
- Shared hosting with 400 other sites on the same server
- No server-side caching so every page request rebuilds from the database
- No CDN so users far from your server wait for the round trip
- Heavy database queries running on every page load
- Unoptimized WordPress with 30 plugins all adding their own database queries
How to Fix TTFB
First, check your TTFB in Chrome DevTools. Open Network tab, load your page, click the main document request. Under "Timing" you'll see "Waiting for server response." If it's over 600ms, you've got a problem.
Quick wins:
- Switch to a faster host (seriously, cheap shared hosting will always be slow)
- Enable server-side caching (WP Super Cache, Redis, Varnish)
- Use a CDN like Cloudflare (free tier is fine for most sites)
- Reduce database queries (deactivate plugins you don't need)
If you're on WordPress and your TTFB is over a second? Your hosting is the problem. No plugin will fix bad hardware. I know nobody wants to hear that, but it's the truth about 80% of the time.
Step 4: Kill Render-Blocking Resources
Your browser can't paint anything until it finishes downloading and parsing all CSS files in the
<head>. If you've got 6 external stylesheets loading before the body, your LCP has to
wait for every single one of them.
CSS Fixes
- Inline critical CSS: Put the styles needed for above-the-fold content directly in a
<style>tag in the head. Load the rest asynchronously. - Remove unused CSS: Most WordPress themes load 200KB of CSS when the page only uses 30KB. Tools like PurgeCSS can strip the unused rules.
- Combine files: 6 small CSS files with 6 round-trips is worse than 1 combined file with 1 round-trip.
JavaScript Fixes
Add defer to every script that isn't absolutely needed for the initial render:
<!-- Bad: blocks rendering -->
<script src="analytics.js"></script>
<!-- Good: loads after HTML is parsed -->
<script src="analytics.js" defer></script>
Analytics, chat widgets, social embeds, tracking pixels. None of these need to load before your hero image appears on screen. Defer them all. For a deeper look at this, check out our JavaScript defer guide.
Step 5: Font Loading
Custom fonts can block text rendering, and if your LCP element is a heading, that's your LCP waiting on a font file to download.
Two fixes that work immediately:
/* In your @font-face declaration */
@font-face {
font-family: 'YourFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* Show fallback text immediately */
}
And preload your most important font file:
<link rel="preload" as="font" type="font/woff2"
href="/fonts/main.woff2" crossorigin>
font-display: swap means the browser shows your text in a system font immediately, then swaps in
the custom font when it's ready. Users see content faster, LCP fires earlier. Win win.
Step 6: Use a CDN
If your server is in New York and a user is in Tokyo, that's about 200ms of network latency just for the round trip. Multiply that by every resource on your page. A CDN caches your files on servers around the world so users always hit a nearby copy.
Cloudflare's free tier is enough for most websites. Set it up, enable caching, and watch your global TTFB drop by 50-80%. For images specifically, if you're using Cloudflare, their Polish feature will automatically convert images to WebP for supporting browsers.
The LCP Fix Checklist
Run Through This Before You Deploy
1. Hero image is WebP (or AVIF), under 150KB
2. Hero image has fetchpriority="high"
3. Hero image is NOT lazy loaded
4. Hero image is preloaded in the head
5. No render-blocking CSS or JS in the head
6. TTFB is under 600ms
7. Fonts use font-display: swap
8. CDN is serving static assets
9. Images use srcset for responsive sizes
10. Width and height attributes set on all images (prevents CLS too)
What About WordPress?
WordPress has some LCP quirks that catch people off guard:
- WP Rocket: Great for caching and JS deferral, but check that it's not lazy-loading your hero image. Go to Media settings and look for "Exclude above-the-fold images."
- Elementor/Divi: These page builders often load massive CSS files. Use their built-in optimization settings or a plugin like Asset CleanUp to remove unused CSS per page.
- WooCommerce: Product images are often the LCP element on product pages. Make sure they're WebP, properly sized, and not lazy loaded.
We've got a dedicated WordPress Core Web Vitals Guide if you want the full breakdown with plugin recommendations and all that.
FAQ
What's a good LCP score?
Under 2.5 seconds. Google uses the 75th percentile of real field data (CrUX) to decide if you pass or fail. Your Lighthouse lab score is useful for debugging but it's not what affects rankings.
My LCP keeps changing between tests. Is that normal?
Totally normal. Lab tools test from a single server at a single moment. Network conditions, server load, and even what other tabs you have open can change the number. Focus on field data trends over time, not individual lab runs.
Does fetchpriority="high" really make a difference?
Yes. We consistently see 200-500ms improvement just from this one attribute. The browser normally downloads images in the order it discovers them. fetchpriority tells it "this one matters most, grab it first."
Should I lazy load my hero image?
No. Absolutely not. Never. Your hero image is the LCP element. Lazy loading it delays its download. Remove
loading="lazy" from any above-the-fold image.
How much does switching to WebP help LCP?
For a typical hero image, switching from JPEG to WebP saves 25-35% of the file size. On a slow connection, that translates to 300-800ms faster LCP. It's one of the easiest wins you can get.
Find Your LCP Problem in 30 Seconds
Run your site through VitalsFixer. We'll show you exactly what your LCP element is, how big it is, and what's blocking it from loading faster.
Analyze My Site Free →Too busy to fix this yourself?
Our engineers fix LCP, CLS, and INP on your site directly. Real humans, not automated tools. 48-hour turnaround. If we can't improve your scores, you get your money back.
View Expert Fix Service →