LCP Subpart Breakdown Analyzer

Decompose Largest Contentful Paint into Chrome's four official subparts, verify that they actually add up, compare every phase against the published 40 / 10 / 40 / 10 budget, and get the exact milliseconds each phase must shed to reach your target. Runs entirely in your browser.

1. Scenario — load a realistic profile or enter your own numbers below

Every preset is internally consistent: the four subparts sum exactly to the stated LCP, which is the invariant the browser guarantees.

2. Input mode

A text LCP element has no resource to fetch, so load delay and load duration are structurally zero and the budget is renormalised over the two phases that remain.

3. Measurements — milliseconds, from the web-vitals attribution build or the DevTools LCP insight

Leave measured LCP at 0 in the first mode to skip the cross-check. The 2500 ms default target is Google's Core Web Vitals "good" threshold at the 75th percentile.

4. Identity check — TTFB + load delay + load duration + render delay = LCP

5. Result

0 ms
LCP used
0 ms
Over target
Dominant bottleneck
0 ms
Its overspend
Good
02500 good4000 poor6000+

6. Proportional timeline — actual against the ideal allocation, both drawn to the same scale

Actual0 ms
0 ms
Ideal allocation at the same LCP0 ms
Budget at your target LCP0 ms
Time to first byte Resource load delay Resource load duration Element render delay

All three bars share one horizontal scale, set by the larger of your actual LCP and the target, so a shorter bar genuinely means a faster page rather than a rescaled drawing.

7. Per-phase audit

Phase Actual Share Ideal share Ideal at this LCP Overspend Overspend (pp) Budget at target Must shed

8. Diagnosis and remediation — ordered by the milliseconds each phase must give back

The method, exactly

Largest Contentful Paint is not a single measurement, it is the end of a chain. Chrome exposes that chain as four consecutive intervals, documented in web.dev's Optimize LCP guide and surfaced programmatically by the attribution build of Google's web-vitals library. The intervals are mutually exclusive and collectively exhaustive, which gives the one hard invariant this page is built around.

TTFB + resourceLoadDelay + resourceLoadDuration + elementRenderDelay = LCP

What each subpart actually measures

For a text LCP element there is no resource, so the middle two intervals are zero by construction and LCP collapses to time to first byte plus element render delay.

The budget this page audits against

web.dev's guidance gives a target distribution rather than absolute per-phase limits, because what is affordable in each phase depends on the total. The recommended allocation is:

Time to first byte ~40% of LCP Resource load delay <10% of LCP Resource load duration ~40% of LCP Element render delay <10% of LCP

The logic behind the shape is that the two 40 percent phases are unavoidable work — a server must respond and bytes must travel — while the two 10 percent phases are overhead. A page spending 45 percent of its LCP in load delay is not slow because of bandwidth; it is slow because the browser did not know what to fetch.

Applied to the 2500 ms "good" threshold this yields a 1000 ms time-to-first-byte allowance. Note that the standalone TTFB recommendation is stricter at 800 ms or less, so the page flags a TTFB above 800 ms even when its percentage share looks acceptable.

How overspend is computed

Two different comparisons are reported, because they answer different questions.

share_i = phase_i / LCP idealMs_i = idealWeight_i x LCP (is the split healthy?) overspendMs_i = phase_i - idealMs_i overspendPP_i = 100 x (share_i - idealWeight_i) budget_i = idealWeight_i x targetLCP (is the page fast enough?) excess_i = max(0, phase_i - budget_i)

The first block is scale-free: it tells you whether the shape of the load is right, even on a page that is already fast. The second block is absolute: it tells you what each phase is allowed to cost if the page is to hit the target at all.

Distributing the required saving

The total saving needed is simply max(0, LCP - target). That saving is allocated across phases in proportion to how far each one exceeds its own target budget:

needed = max(0, LCP - targetLCP) shed_i = needed x excess_i / sum(excess)

This allocation has a useful property. Because the budgets sum exactly to the target, the sum of the positive excesses is always at least the needed saving, so every shed_i is bounded by excess_i. No phase is ever asked to go below its own budget, and phases already inside budget are never asked for anything. A phase that is under budget acts as slack: if time to first byte is already lean, the analyzer will not pretend the fix has to come from the server.

Validation, not silent acceptance

Real measurements get transcribed wrong, mixed across sessions, or pulled from different percentiles. The identity is checked explicitly and the signed residual is reported:

residual = measuredLCP - (TTFB + delay + duration + render)

In solve mode the missing subpart is recovered by subtraction, and a negative result is reported as an error rather than clamped away.

The late-discovery signature

When resource load delay exceeds 25 percent of total LCP — two and a half times its recommended ceiling — the cause is almost never the network. It is one of a small, well-known set of discoverability failures:

Reading the real numbers off your own page

The attribution build of web-vitals returns all four values directly. Chrome DevTools shows the same split in the Performance panel's LCP insight.

import {onLCP} from 'web-vitals/attribution'; onLCP(({value, attribution: a}) => { console.log('LCP ', Math.round(value)); console.log('TTFB ', Math.round(a.timeToFirstByte)); console.log('resourceLoadDelay ', Math.round(a.resourceLoadDelay)); console.log('resourceLoadDuration', Math.round(a.resourceLoadDuration)); console.log('elementRenderDelay ', Math.round(a.elementRenderDelay)); console.log('element ', a.element, a.url); });

In web-vitals v3 the third field was named resourceLoadTime; it became resourceLoadDuration in v4. Field data from the Chrome UX Report does not include this split, so the subparts always come from your own lab or RUM instrumentation.

Thresholds used on this page

All Core Web Vitals thresholds are evaluated at the 75th percentile of page loads, segmented separately for mobile and desktop. Feeding this tool a median or an average will produce a diagnosis of a page load that nobody actually experienced.

Frequently asked questions

My four subparts do not add up to the LCP my monitoring reports. Which is wrong?
Neither, usually. Within a single observation the identity is exact by construction. It breaks when values are aggregated separately: the 75th percentile of TTFB and the 75th percentile of render delay come from different page loads, and percentiles do not add. Aggregate the subparts only from the same set of observations, or compare a single representative trace.
Is a 40 percent time-to-first-byte share really acceptable?
As a share, yes. In absolute terms it depends on the total. At an LCP of 2500 ms a 40 percent share is 1000 ms, which already exceeds the standalone 800 ms TTFB recommendation, so this page flags it separately. At an LCP of 1500 ms the same share is 600 ms and is genuinely fine. Percentages diagnose the shape, milliseconds decide whether you pass.
Why does the tool refuse a negative subpart instead of clamping it to zero?
Because a negative residual means the inputs are mutually impossible: the four intervals are consecutive and non-overlapping, so they can never sum to more than the LCP they compose. Clamping would hide a data-collection bug and produce a confident, wrong recommendation. The tool reports the size of the contradiction instead.
My LCP element is text. Does the 40 / 10 / 40 / 10 budget still apply?
Not directly, because two of its four phases cannot exist. Select the text element type and the analyzer renormalises the published weights over the two remaining phases, giving 80 percent time to first byte and 20 percent element render delay. That renormalisation is derived here, not published by web.dev, and it is labelled as such in the audit table.
Load duration is 90 percent of my LCP. Is that bad?
It is out of shape but it is the least alarming way to be slow, because it is the one phase where the cost is real work rather than waiting. It usually means the hero image is far larger than the space it is displayed in, is served in a legacy format, or is coming from an origin with no CDN in front of it. Correctly sized responsive sources plus AVIF or WebP routinely remove most of it.
Can preloading the LCP image make things worse?
Yes. A preload competes for bandwidth and connection slots with everything else the browser needs early, including the stylesheet that must arrive before anything paints. Preloading is the right fix when load delay is genuinely large; when load delay is already small, a preload just reshuffles contention and can push render delay up. Fix discoverability first, and use fetchpriority="high" on a normal img tag before reaching for a preload.

About the author. Built by , a solo developer shipping performance and developer tooling across the Zovo network. This analyzer implements the LCP subpart model documented by Chrome's web.dev performance guidance and exposed by the web-vitals attribution build. Everything is computed client side; nothing you type leaves the page.

Last updated 29 July 2026

By the same builder: GitHub — theluckystrike BeLikeNative — Grammar AI EarlyThunder — Dev Blog Zovo — AI Dev Tools