// blog / speed

How I fix a slow Largest Contentful Paint

I fix a slow Largest Contentful Paint by finding the LCP element first, because the fix depends on what it is: usually a hero image, a heading or a background image. Then I split its time into four parts and fix the biggest one, which on WordPress is often a lazy loaded hero, a late image or a slow server.

By Muhammad Younus Updated 10 min read Speed

Speed and Core Web Vitals: illustration for "Fixing a slow Largest Contentful Paint"
// 01 - the metric

What is Largest Contentful Paint measuring?

Largest Contentful Paint is the time from the start of navigation to the moment the biggest image, text block or video in the viewport finishes rendering. It's a loading metric, but it isn't the whole load. It only cares about the one thing a visitor most likely came to see. So when it's slow, I start from that one element rather than from a speed score, which is how the Core Web Vitals optimization service I run works, because a lab score on a fast laptop and the LCP your real visitors get aren't the same number.

Google's thresholds are read at the 75th percentile of page loads, so a page can feel quick to you and still fail, because it's judged on the slower end of real visits, not the average.

LCP thresholds at the 75th percentile, and what each one tells me
LCP at the 75th percentileStatusWhat I do with it
2.5 seconds or lessGoodNothing to fix on this metric. I look at INP and CLS on the same template instead.
Over 2.5 and up to 4 secondsNeeds improvementI find the one subpart that's out of proportion and start there.
Over 4 secondsPoorI expect more than one cause, so I measure all four parts before touching anything.

Search Console's Core Web Vitals report uses real visitor data from the Chrome UX Report and groups pages with a similar experience together. So an LCP issue longer than 4s tends to show up against a whole template, every blog post or every product page, rather than one URL. That tells me the fault lives in the template, and one fix can move the whole group.

// 02 - lcp and fcp

How is Largest Contentful Paint different from First Contentful Paint?

First Contentful Paint marks the first text or image the browser paints, even a logo or a line of navigation. Largest Contentful Paint waits for the biggest image or text block in the viewport, which is usually what the visitor came for. So a page can paint early, pass FCP and still fail LCP.

web.dev puts a good FCP at 1.8 seconds or less and a poor one over 3 seconds, against 2.5 and 4 for LCP. FCP isn't a Core Web Vital, so Search Console's report grades LCP, not FCP. The gap between the two is what I read: web.dev says a large one means the LCP resource isn't available to the browser straight away, or the browser is busy with other work before it can show it.

// 03 - the element

How do I find the LCP element on a page?

I don't guess, because the element decides everything after it. The browser only considers images, images inside an SVG, video posters, background images loaded through CSS url(), and block-level text. Elements with an opacity of 0 don't count, and neither do ones that cover the whole viewport.

  1. Step 01

    Start with the failing group in Search Console

    I open the Core Web Vitals report, pick the mobile or desktop group that's failing and take an example URL from it. That's real visitor data, so it beats testing the homepage by habit.

  2. Step 02

    Run that URL through PageSpeed Insights

    The Lighthouse diagnostics name the Largest Contentful Paint element and show the markup behind it, which tells me straight away whether I'm dealing with an img, a heading or a div with a background.

  3. Step 03

    Record a trace in Chrome DevTools

    The Performance panel's trace breaks LCP into its subparts. I throttle the network and CPU, because on a fast desktop connection the problem can simply hide.

  4. Step 04

    Check it again at phone width

    The LCP element on mobile can be a different element from desktop. A hero image that fills a wide screen can drop below the headline on a phone, and then the headline's the thing to fix.

  5. Step 05

    Search the raw HTML for it

    I search the page source, not the rendered DOM, for the image's file name. If it's there with a plain src, the browser can find it early. If it only lives in a stylesheet or gets added by a script, that's the first clue.

// pro tip

The browser stops reporting new LCP candidates once the visitor taps, scrolls or presses a key. When I test, I load the page and leave it alone until it's finished, or the reading's cut short.

Rather have me do this? One person, whole job.

Book a call on Upwork
// 04 - the four parts

Which of the four parts of LCP is slow?

web.dev splits every LCP into four subparts that add up to the whole time. If you're asking how to improve LCP, this is the step that turns one slow number into a cause you can act on.

The four LCP subparts, web.dev's suggested share of the total, and what I check on WordPress
SubpartWhat it coversSuggested shareWhat I check on WordPress
Time to first byteFrom the start of navigation to the first byte of the HTMLAbout 40%Page caching for logged-out visitors, hosting, slow database queries
Resource load delayFrom the first byte to the moment the browser starts fetching the LCP imageUnder 10%Lazy loading on the hero, images set in CSS, images added by JavaScript
Resource load durationHow long the LCP image itself takes to downloadAbout 40%Image dimensions, format and compression, and whether a CDN serves it
Element render delayFrom the image arriving to the element actually paintingUnder 10%Render-blocking CSS and scripts, web fonts, entrance animations

The two delays are where I look first. web.dev's point is a simple one: any time before LCP when neither the HTML nor the LCP image is downloading can be won back. If a page spends most of its LCP waiting for the browser to discover the image, a faster host won't fix it. The browser needs to find the image sooner.

// 05 - wordpress causes

Which WordPress causes do I see most?

Each of these maps to a subpart above, so the trace tells me which one to open first.

  1. i.

    The hero image is lazy loaded

    web.dev is blunt: never lazy load the LCP image. Since WordPress 6.3, core leaves loading="lazy" off the first few images and adds fetchpriority="high" to the one it thinks is the LCP, but only for images its own functions print. A slider, theme or plugin that writes its own markup, or a script that swaps src for data-src, sits outside that. Load delay.

  2. ii.

    The hero is a CSS background

    web.dev lists a CSS background image as one of the cases the browser can't discover from the HTML, so it waits for the stylesheet first. I test the real page before deciding whether it becomes an img or gets a preload. Load delay.

  3. iii.

    The server is slow to send the HTML

    No front-end fix beats a slow first byte. I check whether logged-out visitors get a cached page and how the uncached response behaves, and the hosting and caching layer is where plenty of poor LCP starts. web.dev also lists distance from the server as a cause of a high TTFB, which a CDN caching the HTML near visitors solves, unless unique URL parameters send every request back to the origin. Time to first byte.

  4. iv.

    The image is far bigger than its slot

    A desktop-sized hero sent to a phone is a download problem. I check the srcset WordPress generated, the compression, the format, since WebP or AVIF makes the same picture a smaller file, and whether a CDN's doing any of that work. web.dev adds a caveat: load duration is rarely the main bottleneck, and a smaller file only helps if render delay doesn't swallow the time saved. Load duration.

  5. v.

    CSS and scripts hold back the first paint

    Stylesheets and synchronous scripts in the head block rendering even after the image has arrived, and on WordPress each plugin tends to add its own. I defer what the first screen doesn't need and trim the CSS that's left. Render delay.

  6. vi.

    An entrance animation hides the hero

    Elements with an opacity of 0 aren't LCP candidates, so a hero that fades or slides in can't count until the animation reveals it. The image can be downloaded and waiting while the metric isn't. Render delay.

// watch out

A speed plugin's lazy load setting can cover every image it finds, and the hero's one of them unless it's excluded. Switching a plugin on can make LCP worse, so I check the hero's attributes in the source after every plugin change.

// 06 - text lcp

What if the LCP element is text, not an image?

Then there's no image to find or download. web.dev counts block-level elements containing text as candidates, measured by the smallest rectangle around the text, so on a page with a big headline and a modest hero the headline often wins, especially on a phone.

In a system font, the two middle parts of LCP are zero, and the whole time is server response plus render delay. With a web font, the font can be the wait: web.dev recommends a font-display value other than auto or block, so the text shows in a fallback font while the web font loads. Either way I read the head of the document, the CSS and fonts that block rendering, rather than chasing images.

// 07 - preload and priority

When do preload and fetchpriority help?

Only once I know the element and the slow subpart. Both of them shorten load delay, and nothing else. If the time's in the server response or in render delay, neither will move the number that matters.

fetchpriority="high" on the LCP img tells the browser to fetch it ahead of the other images it has found. web.dev warns that setting high priority on more than one or two images makes the setting unhelpful, so a page that marks its logo, every slide and the hero as high has undone it.

A rel="preload" link in the head helps when the image isn't in the HTML at all: a CSS background, or an image a script adds later. I make sure it asks for the same file the page will use at that screen width, otherwise it's an extra download fighting the real one. Where I can, I'd rather move a background hero into a real img tag, so there's no hint to keep in step.

// 08 - after the fix

How do I know the fix actually worked?

I check twice. The lab trace answers the same day: the subpart I worked on should shrink, and the LCP element should be the one I expected.

The field data is the one that counts, and it's slower. Search Console's report is built on real visits, so it can't move until enough visits after the change have been collected. When I click Start Tracking on the issue, Google runs a 28 day monitoring session before it says whether the fix passed. I won't promise a date.

A change that speeds up the first screen by moving scripts around can shift the layout or slow down interaction, so I watch CLS and INP on the same template while the LCP fix settles. I work alone and remotely, so the person who measured the problem is the one who checks the result.

// when to hand this over

If a template's stuck in Poor and you'd rather one person found the element, measured the four parts and fixed the slow one, that's the Core Web Vitals work I do. Here's how an engagement runs, from the first trace to the field data.

Book a call on Upwork

Is this your site? Send me the URL.

Book a call on Upwork
// questions - speed

What else do people ask about this?

What is a good Largest Contentful Paint time?

2.5 seconds or less, measured at the 75th percentile of page loads. Over 4 seconds is poor, and the band in between needs improvement. Search Console's Core Web Vitals report uses the same thresholds.

Why is my LCP fine in PageSpeed Insights but failing in Search Console?

Because they measure different things. The lab test is one load on a simulated device, while Search Console reports what real visitors got. When they disagree, I trust the field data and try to reproduce it with throttling.

Does a caching plugin fix a slow LCP?

It helps the server response part, and only that part. If the delay is a lazy loaded hero or a blocking script, caching won't touch it, and the plugin's own lazy load setting can make the hero arrive later, not sooner.

// sources - checked

Where do these rules come from?

Platform and Google documentation, opened and read on the day this was written. Everything else is how I work.

  1. web.dev: Largest Contentful Paint (LCP)
  2. web.dev: Optimize Largest Contentful Paint
  3. web.dev: First Contentful Paint (FCP)
  4. Make WordPress Core: image performance enhancements in WordPress 6.3
  5. Search Console Help: Core Web Vitals report
Muhammad Younus, WordPress technical SEO consultant
// who wrote this

One consultant, writing down his own method

I'm Muhammad Younus, a WordPress technical SEO consultant with 8+ years of experience and 100+ client sites fixed. Top Rated Plus on Upwork with a 100% Job Success Score. Every post here is the way I actually do the job, written in the first person because it's one person doing it, remotely, with no team behind the name.

Nothing in these posts is a client I can't name or a number I can't show. Where a figure comes from a client's Search Console, it's from a published case study, and where a rule comes from Google or a platform, the source is linked at the bottom of the post.

// the service behind this post
Muhammad Younus, WordPress technical SEO consultant, smiling warmly at the camera
// next step

Want this done on your own site?

Book a call on Upwork, send the URL and read-only Search Console access, and you'll get a written scope for the search side before any work starts. One person does the whole job, remotely.

Book a call on Upwork