Metricum Lab

Web Performance · LCP · Root-cause debugging

How to Fix Largest Contentful Paint (LCP): Diagnose TTFB, Load Delay, Load Time & Render Delay

I do not start an LCP investigation by compressing every image or chasing a Lighthouse score. I start with the real LCP element and field signal, then ask where the time is actually spent: server response, discovery, transfer, or rendering. The fix should target that subpart—and production field data should decide whether it worked.

Yurii Pekach Technical SEO & Web Performance ConsultantPublishedUpdated31 min read
Largest Contentful Paint diagnostic timeline splitting LCP into TTFB, resource load delay, resource load duration and element render delay, with a prioritized hero element.

The short answer

Fix LCP by decomposing it before optimizing it. Confirm the real LCP candidate, then split its time into TTFB, resource load delay, resource load duration and element render delay. A large image can absolutely be the problem—I have encountered LCP images above 4–5 MB—but field data shows that slow origins often lose even more time before the image request starts. Make the meaningful hero content cheap to render, make critical resources discoverable and correctly prioritized, remove server/rendering bottlenecks, and validate the change in RUM/CrUX rather than declaring victory from one lab run.

≤2.5 s

Good LCP

The current good threshold is LCP at or below 2.5 seconds at the 75th percentile, evaluated separately for mobile and desktop.

1.29 s vs 350 ms

Poor-LCP field median

In Google's image-LCP field analysis, median p75 resource load delay for poor-LCP origins was about 1.29 s versus about 350 ms of image load duration.

≤0.8 s

TTFB rough guide

web.dev recommends 0.8 seconds or less as a rough TTFB guide for most sites; TTFB itself is not a Core Web Vital.

Diagnostic model

Fix LCP by finding where the 2.5-second budget is spent

The metric is one number, but the root cause is normally one or more distinct phases.

LCP is the render time of the largest eligible image, text block or video visible in the viewport. A good result is ≤2.5 seconds at p75. The useful debugging move is to stop treating that number as one opaque delay. I split it into TTFB → resource load delay → resource load duration → element render delay. That immediately changes the investigation from ‘make the page faster’ into a falsifiable question: which phase is consuming the time?

Custom diagram

The four LCP subparts are a root-cause map

A timeline from navigation to the final LCP paint: server response, waiting before the LCP request, resource transfer and waiting before the element can render.

The four LCP subparts are a root-cause mapA timeline from navigation to the final LCP paint: server response, waiting before the LCP request, resource transfer and waiting before the element can render.TTFBnavigation → first byteLoad delayfirst byte → requestLoad durationrequest → response endRender delayresource ready → paintLCP = TTFB + resource load delay + resource load duration + element render delay
Treat the four phases as diagnostic buckets. The biggest bucket is usually the first place to investigate—not necessarily the image bytes.

Candidate first

Confirm the real LCP element before optimizing anything

You cannot configure an LCP selector, but your hero architecture strongly influences which eligible element becomes the largest visible candidate.

Saying that an LCP element can be ‘chosen’ sounds odd, but there is an important engineering truth behind it. The browser chooses from eligible visible content and can replace the candidate as larger content renders. Developers choose the hero composition, visual hierarchy, dimensions, rendering order and whether the dominant content is a text block, image, video poster or background image. In practice, I have redesigned hero sections so the meaningful primary content is a fast text block rather than forcing a huge decorative image to dominate the viewport. That is legitimate information architecture; shrinking or hiding useful content only to game LCP is not.

Identify the candidate and prove why it wins

  1. 1

    Record the affected route and viewport

    Use the device/viewport that approximates the affected field cohort. Candidate size depends on what is visible in the viewport.

    Pass criterionThe test notes include route, viewport and test conditions.

  2. 2

    Open an LCP trace

    Record the page in Chrome DevTools Performance and select the LCP marker or the LCP insight.

    PathChrome DevTools → Performance → Record → Insights → LCP by phase / LCP badge

    Pass criterionThe Details pane identifies the LCP candidate and timing breakdown.

  3. 3

    Inspect the visible hierarchy

    Compare the LCP candidate with the actual primary hero content. Ask whether a decorative media asset is unnecessarily larger than the information the user came to see.

    Pass criterionYou can explain why this element is the largest meaningful eligible content, or document a design reason to change the hero hierarchy.

  4. 4

    Retest after hero changes

    LCP candidates can change as later content renders. After changing layout or media, record a new trace and verify the final candidate rather than assuming it stayed the same.

    Pass criterionThe final LCP candidate is explicitly confirmed in the new trace.

Custom diagram

Design the hero so the primary content is cheap to become LCP

Two hero architectures: a heavy decorative media element dominates the viewport versus a meaningful fast text-led hero with supporting media that does not unnecessarily become the largest candidate.

Design the hero so the primary content is cheap to become LCPTwo hero architectures: a heavy decorative media element dominates the viewport versus a meaningful fast text-led hero with supporting media that does not unnecessarily become the largest candidate.4–5 MB decorative hero?largest visible candidateredesignMeaningful text herofast, stable primary contentsupporting mediaDo not hide important content to game LCP. Make the real visual hierarchy efficient.
The browser still selects the LCP candidate. Your layout determines what content is eligible, visible and largest enough to compete.

Evidence before fixes

Use the four subparts to decide what to fix first

The same 4-second LCP can require completely different engineering work depending on which phase is dominant.

Google’s field analysis is a useful warning against single-cause thinking. For origins with poor image LCP, the median p75 breakdown was about 2,270 ms TTFB + 1,290 ms image load delay + 350 ms image load duration + 360 ms render delay. The majority of poor-LCP origins spent under 10% of p75 LCP time actually downloading the LCP image. That is population-level evidence, not a diagnosis of your page—but it shows why ‘compress the hero’ can be correct and still miss the biggest bottleneck.

Read the dominant LCP subpart as a hypothesis generator
Dominant subpartWhat it usually points towardFirst evidence to inspectTypical first move
TTFBRedirects, connection latency, cache miss, slow origin/backendNavigation timing, CDN/cache status, Server-Timing where availableShorten redirects; improve caching/CDN/origin response
Resource load delayLCP asset discovered late or has weak priorityNetwork initiator chain, request start relative to HTML, Priority columnExpose the asset in HTML/preload and prioritize the real LCP resource
Resource load durationToo many bytes, poor dimensions/format, slow deliveryTransferred bytes, selected srcset candidate, cache/CDNServe the right dimensions/format and reduce transfer cost
Element render delayRender-blocking CSS, fonts, main-thread work, client rendering/hydrationPerformance trace after resource responseEndRemove the blocking work or render meaningful hero HTML earlier

Resource load duration

When the LCP image is huge, fix the bytes—but do not stop there

Oversized images remain one of the simplest and most common regressions on sites without a disciplined media pipeline.

The most ordinary LCP failure I encounter is still an oversized image. On smaller ecommerce and content sites where uploads accumulate without a performance budget, I have seen LCP images exceed 4–5 MB. That number is my project observation, not a recommended threshold. The diagnosis is simpler: compare the rendered dimensions with the selected source candidate and transferred bytes. If a 700 px hero is downloading a multi-megabyte 3000 px original, you have an avoidable transfer problem.

Reduce image transfer cost without breaking visual quality

  1. 1

    Measure the actual selected candidate

    In DevTools Network, inspect transferred size and the resource selected by srcset/sizes at the target viewport and DPR.

    PathChrome DevTools → Network → Img → LCP request

    Pass criterionThe evidence records rendered size, selected source and transferred bytes.

  2. 2

    Generate right-sized variants

    Provide responsive candidates rather than shipping one oversized master to every viewport. Use modern formats when they materially reduce bytes and preserve quality.

    Pass criterionThe target viewport selects a candidate close to its display requirement instead of the full master asset.

  3. 3

    Reserve geometry

    Keep width and height (or an equivalent reserved aspect ratio) so image optimization does not create layout instability.

    Pass criterionThe image has reserved dimensions and the change does not introduce CLS.

  4. 4

    Re-measure the LCP subparts

    Verify that resource load duration actually fell and check whether the bottleneck moved to load delay or render delay.

    Pass criterionThe before/after trace shows the intended subpart reduction without a new dominant delay.

STATICALLY VERIFIED · Responsive LCP image with explicit geometry and priority

<picture>
  <source
    type="image/avif"
    srcset="/hero-640.avif 640w, /hero-1280.avif 1280w"
    sizes="(max-width: 720px) 100vw, 1200px">
  <img
    src="/hero-1280.webp"
    srcset="/hero-640.webp 640w, /hero-1280.webp 1280w"
    sizes="(max-width: 720px) 100vw, 1200px"
    width="1200"
    height="675"
    fetchpriority="high"
    alt="Describe the meaningful hero content">
</picture>

Markup syntax checked on 2026-09-08. Replace file paths, breakpoints, dimensions and alt text with values from the actual layout. Do not add high priority to multiple competing images.

Resource load delay

Make the LCP resource discoverable early—and give only the critical asset high priority

A 200 KB image requested 1.3 seconds late can lose more LCP time than a larger image discovered immediately.

Resource load delay is the time after TTFB but before the LCP resource request starts. Common causes are an image injected by JavaScript, a source hidden behind a lazy-loader data attribute, or a CSS background that cannot be discovered until CSS is fetched and parsed. Google’s field analysis also found a clear relationship between dependency-chain length and LCP: median LCP in the cited dataset rose from about 2.15 s with zero dependent requests to 2.54 s with one and 2.85 s with two. Make the browser discover the real LCP resource as close to the initial HTML as the architecture allows.

Custom diagram

Direct discovery beats a late request chain

A fast path where the parser sees the LCP image immediately versus a delayed path where HTML waits on CSS or JavaScript before the browser learns the image URL.

Direct discovery beats a late request chainA fast path where the parser sees the LCP image immediately versus a delayed path where HTML waits on CSS or JavaScript before the browser learns the image URL.Early discoveryHTML parsersees <img>LCP request startshigh relative priorityPaintshort delayLate discovery chainHTMLCSS / JSdiscover URLafter dependencyLCP fetch
Preload is primarily about discovery; fetchpriority is a relative priority hint. They solve different parts of the loading problem.

STATICALLY VERIFIED · Preload only when the LCP asset is otherwise discovered late

<!-- Best case: the LCP image is directly discoverable in HTML. -->
<img src="/hero.webp" width="1200" height="675" fetchpriority="high" alt="...">

<!-- If the LCP is a CSS background or otherwise not parser-discoverable: -->
<link
  rel="preload"
  as="image"
  href="/hero-background.webp"
  fetchpriority="high">

Markup syntax checked on 2026-09-08. Do not blindly preload an image already discovered early, and make sure preload URL/type/candidate matches the resource the page actually uses to avoid duplicate or wasted fetches.

Network contention

Stop near-fold images from competing with the real LCP

Offscreen does not always mean ‘not requested yet’; browser heuristics can still let nearby images compete for bandwidth.

Another pattern I check on image-heavy pages is competition from media just below the first viewport. It can look harmless in the design, yet several nearby images may begin fetching early and consume bandwidth while the hero is still loading. Browser lazy-loading uses a calculated distance from the viewport rather than a hard ‘below the fold’ line. Chrome’s Fetch Priority guidance specifically calls out carousels where offscreen images may be considered close enough to load; lower priority can be appropriate for those noncritical neighbors.

Prioritize image requests by initial user value, not by DOM order alone
Image roleTypical loading choicePriority hintWhat to verify
Actual LCP heroEager / normal parser discoveryHigh when it is truly criticalStarts early; no competing high-priority images
Second/third carousel slideDepends on UX; may still load earlyLow can be appropriateDoes not delay first visible slide/LCP
Clearly below-fold editorial/product imageLazyAuto or LowNo initial network contention; appears before user reaches it
CSS background LCPPreload because parser cannot see it in HTMLHigh on the preload when justifiedOne matching request starts early; no duplicate fetch

Server + render path

Fix TTFB and render delay before blaming the image pipeline

If the HTML arrives late or the page cannot paint after the LCP resource is ready, image compression has diminishing returns.

TTFB sits in front of every later LCP phase. web.dev gives ≤0.8 s as a rough guide for most sites, while stressing that TTFB is not itself a Core Web Vital. In the field dataset above, poor-LCP origins had a median p75 TTFB of about 2.27 s—already close to the entire good-LCP budget. At the other end, element render delay appears when the LCP resource is ready but CSS, fonts, long main-thread work, client rendering or hydration still prevent the element from painting.

Separate server delay from render delay

  1. 1

    Inspect navigation timing first

    Record TTFB and check redirects, CDN/cache behavior and origin response. A slow HTML response shifts every later phase.

    Pass criterionYou have a before value and can state whether TTFB is materially constraining the 2.5-second target.

  2. 2

    Check whether the LCP resource is already ready

    In the trace, compare resource completion with the LCP paint. A large gap after the resource response indicates render delay, not download delay.

    PathChrome DevTools → Performance → LCP by phase → Element render delay

    Pass criterionThe trace shows whether the element waits after the resource is available.

  3. 3

    Trace the blocking work

    Inspect render-blocking styles, web-font behavior, long tasks and JavaScript-driven rendering/hydration inside that interval.

    Pass criterionAt least one observed blocker overlaps the render-delay window; do not infer causality from file size alone.

  4. 4

    Move meaningful hero output earlier when architecture allows

    For client-rendered pages, prefer server-rendered/static meaningful hero HTML when it removes a real render dependency. Measure the trade-off rather than assuming SSR is automatically faster.

    Pass criterionThe changed architecture paints the same meaningful content earlier in the trace and does not regress other CWV/UX signals.

Field attribution

Collect enough RUM attribution to know which LCP phase fails for real users

CrUX confirms the field problem; first-party attribution can connect poor LCP to the target, resource and timing phase.

The `web-vitals` attribution build exposes the LCP target, image URL where applicable and the same four timing components used in this guide. That is far more actionable than collecting only `LCP=4.1s`. Keep the payload privacy-safe and normalized; the goal is to group templates, releases and candidate types, not to collect page text or user identifiers. For the broader field-vs-lab methodology, use the Core Web Vitals testing guide.

STATICALLY VERIFIED · Capture LCP target and four timing subparts with web-vitals attribution

import {onLCP} from 'web-vitals/attribution';

onLCP(({value, rating, id, attribution}) => {
  const payload = {
    value,
    rating,
    id,
    path: location.pathname,
    target: attribution.target ?? null,
    resource: attribution.url ?? null,
    timeToFirstByte: attribution.timeToFirstByte,
    resourceLoadDelay: attribution.resourceLoadDelay,
    resourceLoadDuration: attribution.resourceLoadDuration,
    elementRenderDelay: attribution.elementRenderDelay
  };

  navigator.sendBeacon(
    '/rum/lcp',
    new Blob([JSON.stringify(payload)], {type: 'application/json'})
  );
});

Syntax checked on 2026-09-08. Requires web-vitals v5 attribution build and a site-specific endpoint. Normalize routes and review consent, sampling, retention and sensitive-data policy before production collection.

Useful LCP RUM dimensions without collecting unnecessary content
FieldDecision it supportsPrivacy/quality note
Normalized route/templateWhich page family is failing?Strip IDs and sensitive query parameters
LCP target/candidate typeIs the regression text, image or another candidate pattern?Prefer stable generated selectors; do not capture DOM text
Four LCP subpartsWhich phase dominates for real users?Aggregate by sufficient sample sizes
Release/build IDDid a deployment introduce or remove the regression?Use a technical build identifier, not a user identifier

Metricum Lab method

My LCP workflow: field signal → candidate → phase → one fix → field validation

The work is complete only when the causal chain is explicit and the production distribution moves in the intended direction.

My preferred order is deliberately conservative. I first confirm that LCP is a real user problem, then identify the candidate and dominant subpart, reproduce that mechanism, change one causal lever and compare the trace again. A faster lab run is the deployment gate—not the final proof. Production RUM can show the result quickly for your measured cohort; CrUX changes more slowly because it is an aggregated field dataset. I also check CLS after hero/image changes and use the JavaScript SEO debugging workflow when rendering architecture is involved.

Run a reproducible LCP fix cycle

  1. 1

    Baseline the field problem

    Record p75 LCP for the affected mobile/desktop population and the measurement window. Add first-party RUM if available.

    Pass criterionThe issue is tied to a real field cohort rather than a single lab score.

  2. 2

    Confirm the candidate

    Record the final LCP element/resource at the target viewport and journey.

    Pass criterionThe investigation names the actual candidate, not an assumed hero asset.

  3. 3

    Name the dominant subpart

    Use the LCP timing breakdown to classify the first root-cause hypothesis as TTFB, load delay, load duration or render delay.

    Pass criterionThe hypothesis is tied to measured milliseconds in one phase.

  4. 4

    Change one causal lever

    Examples: right-size the selected image candidate, expose the resource in HTML, adjust priority, shorten server response or remove observed render blocking.

    Pass criterionThe patch can be connected to the dominant subpart without unrelated performance changes.

  5. 5

    Repeat the same lab conditions

    Re-run equivalent traces to distinguish a real subpart reduction from normal run-to-run variation.

    Pass criterionThe intended subpart improves across repeated comparable runs and no major regression appears elsewhere.

  6. 6

    Validate after release

    Watch first-party RUM by route/release; then use CrUX/Search Console as slower external field confirmation. Do not treat the rolling field dataset as an instant deployment test.

    Pass criterionThe production cohort moves in the expected direction with adequate sample and no hidden device/template regression.

Primary sources and documentation

Sources

Every changing search, browser, interface or technical-behavior claim in this guide is tied to a current primary source.

  1. web.dev / Chrome team Largest Contentful Paint (LCP) (opens in a new tab)Current LCP definition, p75 threshold, eligible elements, candidate behavior and field/lab measurement guidance.
  2. web.dev / Chrome team Optimize Largest Contentful Paint (opens in a new tab)Canonical four-subpart LCP model and guidance on TTFB, resource discovery, priority, load duration and render delay.
  3. web.dev / Chrome team Common misconceptions about how to optimize LCP (opens in a new tab)Field-data analysis of LCP subparts across CrUX origins, including p75 medians and request-chain relationship.
  4. Chrome for Developers Performance insights: Get actionable insights on your website's performance (opens in a new tab)Current DevTools path for LCP details and the Timings breakdown into TTFB, load delay, load time and render delay.
  5. Chrome for Developers Insights sidebar in the DevTools Performance panel (opens in a new tab)Current LCP by phase workflow and optional field p75 subpart overlay in Performance Insights.
  6. web.dev / Chrome team Optimize resource loading with the Fetch Priority API (opens in a new tab)Resource-priority behavior, LCP-image prioritization, carousel/near-fold competition and preload versus fetchpriority guidance.
  7. MDN Web Docs fetchpriority HTML attribute (opens in a new tab)Current standardized high/low/auto priority hint semantics and warning to use the hint sparingly.
  8. MDN Web Docs <img>: The Image Embed element (opens in a new tab)Current image loading, fetchpriority, width/height and lazy-loading semantics.
  9. MDN Web Docs Responsive images (opens in a new tab)Responsive image selection with srcset and sizes for serving appropriately sized assets.
  10. web.dev / Chrome team Time to First Byte (TTFB) (opens in a new tab)TTFB definition, lab/field measurement and the current rough guide of 0.8 seconds or less for most sites.
  11. GoogleChrome / GitHub web-vitals library README (opens in a new tab)Current v5 attribution build and LCP attribution fields including target, URL and four LCP subparts.

Need help diagnosing and implementing the fix?

Turn LCP from a score into an implementation-ready root-cause backlog

Metricum Lab can connect field LCP regressions to server timing, resource discovery, image delivery, render-path traces and post-release validation—then prioritize the fixes that actually reduce user-visible delay.

Explore Core Web Vitals Optimization