When phones report slow LCP, the practical question is not “how do I speed up the site” but “which visible element are users waiting for, and which stage of its load path dominates?”
Implementation (overview)
- Start with field data (CrUX, Search Console, or your RUM) to confirm the complaint on phones and to identify the affecting URL and likely LCP element. Field data answers whether real users see a problem; don’t skip it.
- Reproduce the same phone-sized viewport and content state in a lab trace to gather navigation and resource timings that explain why the field signal exists.
- Break LCP into four subparts: Time to First Byte (TTFB), resource discovery/load delay, resource download duration, and element render delay. Each subpart points at a different ownership domain (server, markup/priority, asset size/network, or client rendering).
- Map the dominant subpart to the smallest useful fix: server or CDN work for TTFB; preload or earlier markup for discovery delay; file format/size or CDN for download duration; reduce render-blocking CSS/JS or avoid hidden-state logic for render delay.
- Apply a single, targeted change and re-measure the same element on the same phone state. Compare subpart distributions, not just the headline LCP number.
Expected behavior
- You’ll get a clear diagnosis explaining which part of the load path caused the visible delay on phones. A well-targeted change should shorten the dominant subpart in lab traces and, when broadly rolled out, align with field improvements for the tested page.
- The article includes a small helper that computes the breakdown and returns a recommendation tied to the dominant subpart.
Limitations
- This workflow is page- and content-shape specific. A fix for a hero-image landing page may not apply to a text-heavy page with the same LCP score but a different dominant subpart.
- Lab traces explain causality but are sensitive to device, CPU contention, and network variability. Always anchor claims to field evidence when possible.
The server will insert a verified implementation example that computes the breakdown and chooses a recommendation for the dominant subpart.
Working example
export function analyzeLCP({ navigationTiming, lcpEntry, resourceTiming } = {}) {
if (!navigationTiming || typeof navigationTiming.responseStart !== 'number') {
throw new TypeError('navigationTiming with numeric responseStart required');
}
if (typeof lcpEntry?.renderTime !== 'number') {
throw new TypeError('lcpEntry with numeric renderTime required');
}
const navigationStart = typeof navigationTiming.navigationStart === 'number'
? navigationTiming.navigationStart
: 0;
const ttfb = Math.max(0, navigationTiming.responseStart - navigationStart);
const hasResource = Boolean(
resourceTiming &&
typeof resourceTiming.startTime === 'number' &&
typeof resourceTiming.responseEnd === 'number'
);
let resourceLoadDelay = 0;
let resourceLoadDuration = 0;
let elementRenderDelay = 0;
if (hasResource) {
resourceLoadDelay = Math.max(0, resourceTiming.startTime - navigationTiming.responseStart);
resourceLoadDuration = Math.max(0, resourceTiming.responseEnd - resourceTiming.startTime);
elementRenderDelay = Math.max(0, lcpEntry.renderTime - resourceTiming.responseEnd);
} else {
elementRenderDelay = Math.max(0, lcpEntry.renderTime - navigationTiming.responseStart);
}
const totalLcpMs = ttfb + resourceLoadDelay + resourceLoadDuration + elementRenderDelay;
const parts = [
{ name: 'ttfb', ms: ttfb },
{ name: 'resourceLoadDelay', ms: resourceLoadDelay },
{ name: 'resourceLoadDuration', ms: resourceLoadDuration },
{ name: 'elementRenderDelay', ms: elementRenderDelay },
];
// When two parts tie, prefer the part that better maps to a developer-actionable
// front-end intervention. In practice we want elementRenderDelay to win ties
// with ttfb so that zero-resource LCPs attribute dominance to rendering.
const priority = {
ttfb: 1,
resourceLoadDelay: 2,
resourceLoadDuration: 3,
elementRenderDelay: 4,
};
const dominant = parts.reduce((a, b) => {
if (a.ms > b.ms) return a;
if (b.ms > a.ms) return b;
// tie: choose higher priority
return priority[a.name] >= priority[b.name] ? a : b;
});
const recommendations = {
ttfb: 'Improve server TTFB: reduce backend work, cache HTML, or use a CDN.',
resourceLoadDelay: 'Start loading the LCP resource earlier: improve discovery or preload it.',
resourceLoadDuration: 'Reduce the resource size or use a more efficient format.',
elementRenderDelay: 'Reduce render-blocking CSS/JS and avoid hiding the LCP element.',
};
return {
totalLcpMs,
breakdown: parts,
dominantSubpart: dominant.name,
recommendation: recommendations[dominant.name],
};
}
Read the full article: https://chriseugenerodriguez.com/blog/diagnose-slow-lcp-on-phones
Top comments (0)