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 at the 75th percentile | Status | What I do with it |
|---|---|---|
| 2.5 seconds or less | Good | Nothing to fix on this metric. I look at INP and CLS on the same template instead. |
| Over 2.5 and up to 4 seconds | Needs improvement | I find the one subpart that's out of proportion and start there. |
| Over 4 seconds | Poor | I 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.
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.
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.
- 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.
- 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 adivwith a background. - 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.
- 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.
- 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.
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 UpworkWhich 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.
| Subpart | What it covers | Suggested share | What I check on WordPress |
|---|---|---|---|
| Time to first byte | From the start of navigation to the first byte of the HTML | About 40% | Page caching for logged-out visitors, hosting, slow database queries |
| Resource load delay | From the first byte to the moment the browser starts fetching the LCP image | Under 10% | Lazy loading on the hero, images set in CSS, images added by JavaScript |
| Resource load duration | How long the LCP image itself takes to download | About 40% | Image dimensions, format and compression, and whether a CDN serves it |
| Element render delay | From the image arriving to the element actually painting | Under 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.
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.
- 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 addsfetchpriority="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 swapssrcfordata-src, sits outside that. Load delay. - 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
imgor gets a preload. Load delay. - 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.
- iv.
The image is far bigger than its slot
A desktop-sized hero sent to a phone is a download problem. I check the
srcsetWordPress 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. - 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.
- 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.
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.
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.
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.
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.
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
