Mini-tutorial: diagnose slow mobile LCP by splitting timings and proving impact

Mini-tutorial: diagnose slow mobile LCP by splitting timings and proving impact

calendar_today ago • schedule2 min read
— Originally published at chriseugenerodriguez.com

Prerequisites

  • Access to field RUM (CrUX, Search Console, or your own real-user metrics) and a lab trace tool capable of recording navigation and resource timing for a phone-sized viewport.

Implementation decision

  • Diagnose by splitting LCP into four actionable subparts: TTFB, resource discovery delay, resource download duration, and element render delay. Use navigationStart as the baseline for TTFB. Reproduce the same phone-sized viewport and content state when you collect the lab trace so the subpart distribution matches the real complaint.

Expected result

  • You’ll identify which subpart dominates the visible delay on phones and get a focused recommendation (for example: cache HTML or move backend work for TTFB; preload or move markup earlier for discovery delay; compress or change format for download duration; inline critical CSS or remove blocking scripts for render delay). A targeted fix should change the dominant subpart in lab traces and — when rolled out — surface in field metrics for the tested page.

One limitation

  • The workflow proves impact only for the precise page, viewport, and content shape you tested. Different pages or hero content can surface different dominant subparts, so repeat the method per page type rather than generalizing.

The server will insert a short verified implementation example that computes the breakdown and selects 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

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

How to Fix Core Web Vitals: LCP, CLS & INP | FloxoLab

floxolab - Oct 16

How to Debug Failed n8n Workflows: Step-by-Step Guide

floxolab - Nov 2

Mini-tutorial: Preload before visibility without jitter

Chris Rodriguez - Oct 29

Should Exact Search Matches Always Come First?

WAQAS AHMAD - Oct 30

Your Service Desk Data Is Smarter Than You Think. AI Is Finally Proving It.

Tom Smithverified - Jun 10
chevron_left
595 Points • 25 Badges
Santa Monica, CA • chriseugenerodriguez.com
21Posts
0Comments
4Connections
10 years fe

Related Jobs

View all jobs →

Commenters (This Week)

6 comments
6 comments
5 comments

Contribute meaningful comments to climb the leaderboard and earn badges!