"Improve your LCP" is useless advice until you know exactly which DOM element the browser is timing. Guessing wrong wastes a sprint optimizing a background image that was never the bottleneck while the real culprit — often a lazy-loaded hero or a late-rendering text block — keeps shipping a slow score to real users.
Method 1: DevTools Performance Panel (Fastest for a Single Page)
Open Chrome DevTools, go to the Performance panel, record a page load, and look at the LCP marker in the timeline. Clicking it opens a summary that includes a direct link to the element in the Elements panel — this is the single fastest way to identify the LCP element for a page you can load locally or on staging.
The Lighthouse panel gives the same identification with less manual digging: run an audit, open the LCP audit entry, and it names the element directly along with a phase breakdown (TTFB, load delay, load time, render delay) showing which phase is actually costing you time. A large image that's slow because of load delay (request started late) needs a different fix than one that's slow because of render delay (image downloaded fine, but something blocked paint).
Method 2: PerformanceObserver in Production Code
DevTools only tells you about your own test load. To find the LCP element across real user sessions, instrument it directly:
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP element:', lastEntry.element);
console.log('LCP time:', lastEntry.startTime);
console.log('LCP url (if image):', lastEntry.url);
}).observe({ type: 'largest-contentful-paint', buffered: true });
The buffered: true option is important — it retrieves entries that fired before your script attached the observer, which matters because LCP candidates can be reported very early in the page lifecycle, before your JS bundle finishes parsing.
Send lastEntry.element (or a stable identifier derived from it, like a data-* attribute or class name — the raw DOM node isn't useful once serialized) to your analytics or RUM pipeline. Do this before you touch anything else: a week of real field data telling you the actual LCP element and its distribution across page types is worth more than any amount of local guessing, and it often surfaces surprises — a cookie banner image, a font-loading text block, or a slow third-party embed nobody suspected. See the web.dev LCP guide for the full entry API and edge cases.
Filtering Out Noise
largest-contentful-paint entries fire multiple times as the browser discovers larger candidates. Only the final entry before the reporting cutoff (first interaction or scroll) is the one that counts toward the actual LCP score — don't log every entry as if each is meaningful, or your data will be dominated by early false candidates like a header logo that gets replaced by a larger hero image half a second later.
Method 3: Field Data from Chrome UX Report (CrUX)
If you don't have RUM instrumentation yet, the Chrome UX Report gives aggregated real-user LCP data for any origin with sufficient Chrome traffic, queryable through PageSpeed Insights or the CrUX API/BigQuery dataset. It won't name the specific DOM element — CrUX reports timing distributions, not element identity — but it tells you whether your lab-measured LCP time is representative of real users at all, which is the first thing worth confirming before deep-diving into element identification.
What to Do Once You've Identified the Element
If It's an Image
Check three things in order: is it preloaded (<link rel="preload" as="image"> for above-the-fold hero images), is it correctly sized for its rendered dimensions rather than oversized and scaled down by CSS, and is it served in an efficient format. Run it through the Image Resizer to match actual rendered dimensions, and the JPG Compressor to check whether quality settings have headroom to reduce weight without visible loss.
If It's a Background Image
CSS background-image LCP candidates can't be preloaded with the standard <link rel="preload" as="image"> trick as directly as an <img> tag — you either need imagesrcset-style workarounds or, better, convert the element to a real <img> with object-fit: cover if the visual result is equivalent, since real image elements get full preload and priority-hint support.
If It's a Text Block
Text-as-LCP usually points to a font-loading problem, not an image problem — if a heading is your LCP element and it's slow, check whether a custom web font is blocking text render (font-display: swap vs. block) or whether the text is waiting on a slow API response before it can render at all.
Decision Point: Lab vs. Field, and When Each Matters
| Question you're answering | Right tool | |---|---| | "What's my LCP element on this exact page right now?" | DevTools Performance/Lighthouse panel | | "What's my LCP element across real users, all devices/networks?" | PerformanceObserver + RUM pipeline | | "Is my site's LCP actually a problem at scale, before I invest engineering time?" | CrUX field data via PageSpeed Insights | | "Which specific optimization will move the needle?" | Combine element identification with the phase breakdown (TTFB/load delay/load time/render delay) from Lighthouse |
Identify the element and its bottleneck phase before optimizing anything — teams regularly spend a sprint compressing an image that turns out to have excellent load time, while the real render delay was a render-blocking stylesheet that had nothing to do with the image itself.