Largest Contentful Paint: Identifying and Optimizing the Right Element
AI generated
60fps
ms
Performance · Core Web Vitals · LCP
Identifying and Optimizing the LCP Element
Finding the actual LCP element and fixing the phase that is really slow

Largest Contentful Paint is widely regarded as the most important of the three Core Web Vitals for perceived load speed, yet in practice it often gets approached wrong, with teams generically trying to make the entire page faster. The first necessary step is identifying the actual LCP element on a page, which is often not the element you would intuitively expect. The second step is breaking the LCP time down into its four phases, TTFB, Load Delay, Load Time, and Render Delay, and optimizing each phase specifically and individually, instead of chasing a diffuse improvement of the total time.

14 min read LCP · Core Web Vitals TTFB · Render Delay

1. LCP as the primary load-time metric of the Core Web Vitals

Largest Contentful Paint measures the point in time when the largest element rendered within the initially visible viewport finishes displaying, usually a hero image, a large heading, or a block-level text element. This metric correlates strongly with the subjective impression of load speed, since it captures exactly the moment a page's main content becomes visible and therefore usable to the visitor, unlike earlier metrics such as First Paint, which could also be triggered by small elements irrelevant to the visitor.

Google defines an LCP score of 2.5 seconds or below as good, with that threshold measured at the 75th percentile of all page views, not the median. That choice means the slower, often mobile or poorer-network page views have to be explicitly accounted for, and an optimization that only improves the average case may barely move the actually measured LCP score at all.

2. Determining the LCP element for a given page

Which element actually counts as the LCP element on a given page cannot be reliably guessed, it has to be read directly from the browser via the PerformanceObserver API using the largest-contentful-paint entry type. This observer fires for every candidate that becomes the largest visible element seen so far, and provides a reference to the concrete DOM element through the element property, letting you determine the actual LCP candidate programmatically instead of assuming it from a visual impression.

In production it is worth capturing this continuously through Real User Monitoring rather than checking it once in a lab, since the LCP element can differ from view to view depending on viewport size, loaded A/B test variants, or personalized content. The example below shows a minimal observer that logs the element and the time value for every new LCP candidate.


// Determine the LCP element and timing via PerformanceObserver
const observer = new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  const latestEntry = entries[entries.length - 1];

  console.log('LCP candidate:', latestEntry.element);
  console.log('Time to this candidate:', latestEntry.startTime);
  console.log('Resource URL (if image):', latestEntry.url || 'no image');
});

observer.observe({ type: 'largest-contentful-paint', buffered: true });

// Finalize the value on the first user interaction or visibility change
['keydown', 'click', 'visibilitychange'].forEach((eventName) => {
  addEventListener(eventName, () => observer.takeRecords(), { once: true, capture: true });
});

3. Why the LCP element is often not the obvious one

A common misconception is assuming the LCP element is automatically the large hero image at the top of the page, when in many real cases a different element takes that role. On a blog article page, for instance, a long, multi-line heading block can occupy more screen area than a smaller header image, which makes the text, not the image, the LCP element and calls for different optimization measures than pure image optimization.

It gets even more surprising on pages with dynamically loaded content, for example a carousel that initially shows a small placeholder image on first render and only swaps in the actual, larger image after JavaScript has loaded. In this case the LCP timestamp can incorrectly land on the small placeholder image, even though the visitor perceives the larger image as the actually relevant content, which shows how important programmatic verification through the PerformanceObserver is compared to a purely visual guess.

4. An overview of the four phases of LCP time

Once the LCP element is known, the total LCP time can be broken down into four clearly distinct phases, each influenced by different technical factors: Time to First Byte, the time until the first server response, Load Delay, the time between TTFB and the start of loading the LCP resource, Load Time, the actual download duration of the resource, and Render Delay, the time between the finished download and the actual rendering on screen.

This breakdown is valuable precisely because each phase gets improved by completely different measures, and a generic goal like making the page faster without knowing the dominant phase almost always targets the wrong spot. A team that puts significant effort into image compression while the Load Delay phase is actually caused by a blocking script barely moves the LCP score, even though objectively valuable work got done.

5. Phase one: tackling TTFB specifically

The TTFB phase is primarily determined by server response time, network latency, and the distance between the visitor and the server, making it the one phase out of the four that is barely influenced by frontend measures, and instead almost entirely by backend and infrastructure decisions. A slow database query, an overloaded application server, or the absence of an edge cache for frequently requested pages fall directly into this phase and can be addressed independently of every other LCP-related measure.

A CDN with edge caching for largely static pages, optimizing slow database queries, and the geographic proximity of the server to the visitor are the most effective levers against a slow TTFB phase. For dynamically generated, personalized pages where full caching is not possible, streaming SSR additionally helps, since the shell can already be sent while personalized data is still loading in the background, lowering the effectively perceived TTFB for the LCP element.

6. Phase two: tackling Load Delay specifically

Load Delay occurs when unnecessary time passes between the HTML document arriving and the actual start of downloading the LCP resource, frequently caused by render-blocking scripts or stylesheets that must load before the LCP resource, or by the resource being discovered late because it only gets inserted into the DOM via JavaScript instead of being present directly in the initial HTML.

The most effective measures against Load Delay are referencing the LCP resource directly and statically in the HTML so the preload scanner discovers it immediately, and avoiding or at least shrinking render-blocking resources that load before the LCP resource. A targeted fetchpriority="high" on the LCP element additionally helps by favoring it over competing resources within the already-early discovery.

7. Phase three: tackling Load Time specifically

Load Time refers to the pure download duration of the LCP resource itself once the request has started, and is primarily determined by file size, compression, and connection bandwidth. For image LCP elements this concretely means using modern formats like AVIF or WebP with reasonable compression, delivering the correctly sized image for the actual viewport via srcset, and serving the resource through a CDN with good geographic coverage.

For text LCP elements, say a large heading, the Load Time phase usually barely registers, since text itself needs very few bytes, unless rendering the text indirectly depends on a still-loading web font. In that case the actual bottleneck effectively shifts into the Load Time phase of the font file, which is why optimizing font loading, for example via preload and a font subset reduced to only the actually used characters, represents the biggest lever here.

8. Phase four: tackling Render Delay specifically

Render Delay is the time between the LCP resource finishing its download and actually appearing on screen, and is frequently underestimated even though it can account for a substantial share of the total time in client-rendered applications. Typical causes include a long, blocking JavaScript task on the main thread that delays actual rendering even though the resource itself has long since downloaded, or an inefficient CSS cascade that only stabilizes the final layout after several recalculations.

The most effective measures against Render Delay are breaking long JavaScript tasks into smaller units so the main thread does not stay blocked over extended periods, and delivering critical CSS for the initially visible viewport inline instead of pulling it from a still-to-be-loaded external file. In React or similar frameworks, it additionally helps to avoid placing the LCP element behind a Suspense boundary with artificial delay when it is critical for the first visible content.

9. Why phase-based diagnosis beats generic optimization

The decisive advantage of phase-based diagnosis over the generic question of making the page faster is that optimization effort can be focused specifically on the actually dominant phase, instead of getting spread evenly across all four phases, where it shows little effect in three out of four cases. Tools like the Chrome DevTools performance panel or specialized web vitals libraries now display the four phases explicitly as separate time segments, making it possible to identify the dominant phase within minutes.

In practice a recurring process works well: after every significant change to a page, the LCP phase distribution gets measured again to check whether the dominant phase has shifted, for instance because a previously good TTFB suddenly became the largest phase due to new, heavier backend logic. This continuous, phase-aware monitoring prevents optimization effort from being invested in a phase that used to matter but is no longer the actual cause of the LCP time.

Phase What it measures Most common cause Most effective fix
TTFB Time until the first server response Slow backend logic, missing edge cache CDN caching, database optimization, streaming SSR
Load Delay Time until the resource download starts Render-blocking scripts, late discovery Static HTML reference, fetchpriority="high"
Load Time Pure download duration of the resource Large file size, missing compression Modern image formats, srcset, CDN with good coverage
Render Delay Time between download finishing and rendering Blocking JavaScript task, late CSS Task splitting, inline critical CSS

Mironsoft

Web performance, Core Web Vitals, and load time optimization

Load times that don't make users bounce before the page is even visible?

We review existing websites for slow Core Web Vitals, bloated JavaScript bundles, and unnecessary render blockers, then build a performance foundation that stays measurable instead of just looking good once.

Performance Audit

Systematically measuring and fixing Core Web Vitals, load waterfall, and render blockers.

Bundle Optimization

Specifically reducing JavaScript and CSS bundle size and improving code splitting.

Monitoring Setup

Establishing continuous performance monitoring instead of a one-time snapshot.

10. Summary

Identifying and Optimizing LCP: The Key Points

Core idea

LCP optimization starts with programmatically identifying the actual LCP element, not a visual guess.

Four phases

TTFB, Load Delay, Load Time, and Render Delay each get improved by completely different measures.

Common mistake

Optimizing image compression while the real delay sits in a different, undiagnosed phase.

Approach

Remeasure the phase distribution after every significant change instead of relying on a one-time diagnosis.

11. FAQ: Identifying and Optimizing LCP: The Key Points

1How do I find the actual LCP element on a page?
Through the PerformanceObserver API using the largest-contentful-paint entry type, which provides a direct reference to the DOM element instead of a visual guess.
2Is the LCP element always an image?
No, it can also be a text block like a large heading, especially when it occupies more screen area than any images present in the viewport.
3Which phase should I optimize first?
The phase with the largest measured share of the total LCP time, determined via the Chrome DevTools performance panel or a web vitals library, not the phase that intuitively seems easiest to fix.
4Can streaming SSR improve the TTFB phase?
Yes, since the static shell can already be sent while personalized data is still loading in the background, the effectively perceived TTFB for the LCP element decreases.
5Why does fetchpriority help with Load Delay?
Because it favors the LCP resource over competing resources within the already-early discovery, letting the actual download start sooner.
6Why could a large heading be affected by a slow web font?
If rendering the text waits on a web font to load, the bottleneck effectively shifts into the Load Time phase of the font file, even though the LCP element itself is text.
7What typically causes Render Delay?
A long, blocking JavaScript task on the main thread, or an inefficient CSS cascade that only stabilizes the layout after several recalculations.
8Does the LCP element change depending on the device?
Yes, depending on viewport size a different element can become the largest visible one, which is why Real User Monitoring across devices matters.
9Is a one-time measurement of the four phases enough?
No, the phase distribution should be remeasured after every significant page change, since the dominant phase can shift due to new backend logic or frontend changes.
10Why does the 75th percentile matter for LCP instead of the average?
Because Google measures the LCP target at the 75th percentile to ensure slower, often mobile page views are explicitly accounted for, instead of only optimizing for the favorable average case.