How to Diagnose and Fix LCP Resource Load Delay

This guide zooms in on one phase of the model in Measuring LCP with Chrome DevTools, within Core Web Vitals & Measurement. LCP for an image splits into four parts: time to first byte, resource load delay, resource load duration, and element render delay. Load delay is the time between the first byte of the HTML arriving and the browser starting to request the LCP image.

It is the phase most often overlooked because nothing is "slow" during it — no request is in flight for the image at all. Yet on real pages it commonly accounts for 30–50% of LCP. A well-optimised page keeps load delay under roughly 10% of total LCP; at the 2.5s budget that means a few hundred milliseconds at most. When the image request starts at 1.4s, it does not matter how small the image is.

LCP phases with a dominant load delay Timeline of an image LCP of 3.1 seconds in which resource load delay accounts for 1.3 seconds before the image request even starts. LCP phases with a dominant load delay Phases TTFB load delay load duration render 0ms 400ms 800ms 1200ms 1600ms 2000ms 2400ms 2800ms 3200ms LCP 2.5s

Rapid Diagnosis

  • Get the breakdown. In the Performance panel, the LCP insight lists the four phases. If "Load delay" is the largest or second-largest, continue.
  • Find the request's initiator. In the Network panel, click the LCP image and open the Initiator tab. "Parser" means discovered from HTML — good. A script file or a stylesheet means late discovery.
  • Check the priority. The Priority column should show "High" from the start. Images start at "Low" and are boosted only after layout confirms they are in the viewport — which may be hundreds of milliseconds later.
  • Check the connection. If the image is on another origin, look at the request's Timing tab for DNS, connection and SSL time. Setup on a cold origin is part of load delay.
  • Look for lazy loading. Inspect the element: loading="lazy" on the LCP image defers the request until layout, by design.

Root Cause Analysis

1. Late discovery. The preload scanner reads raw HTML ahead of the parser and starts fetches for <img src>, srcset, and <link rel=preload>. It cannot see images set by JavaScript, CSS background-image, images rendered by a client framework, or data-src lazy-loading patterns. Those requests start only after scripts or styles run.

2. Low initial priority. Even when discovered early, images begin at low priority. With HTTP/2, the server and browser schedule higher-priority CSS, fonts and scripts first. On a congested connection, the image waits.

3. Connection setup on a separate origin. An image CDN on its own hostname needs DNS, TCP and TLS before the first byte can be requested — 100–300ms on mobile networks, more on high-latency links.

4. Lazy loading applied to the LCP image. Native loading="lazy" waits until layout determines the image is near the viewport. JavaScript lazy loaders wait longer still: for the script, then for an IntersectionObserver callback.

Why did the LCP request start late? Decision sequence for identifying the cause of resource load delay from the Network panel initiator, priority and timing data. Why did the LCP request start late? Initiator is a script or stylesheet? Late discovery — put the URL in HTML or preload it yes no Has loading=lazy or a JS lazy loader? Remove lazy loading from the LCP image yes no Priority starts at Low? Add fetchpriority=high yes no DNS + connect + TLS over 100ms? Preconnect or serve from the main origin yes no Check for bandwidth contention from other early requests

Step-by-Step Resolution

1. Make the LCP image discoverable in the HTML

Render a real <img> with a real src (and srcset) in the server response. If the image must remain a CSS background or is chosen by JavaScript, add a preload.

html
<!-- Preferred: a plain img in the HTML is found by the preload scanner. -->
<img src="/img/hero-1200.avif" srcset="/img/hero-640.avif 640w, /img/hero-1200.avif 1200w"
     sizes="(max-width: 700px) 100vw, 1200px" width="1200" height="630" alt="…">

<!-- When it cannot be: preload with the same srcset/sizes so the right file is fetched. -->
<link rel="preload" as="image" href="/img/hero-1200.avif"
      imagesrcset="/img/hero-640.avif 640w, /img/hero-1200.avif 1200w"
      imagesizes="(max-width: 700px) 100vw, 1200px">
<!-- trade-off: a preload whose srcset differs from the eventual img fetches a
     DIFFERENT file and doubles the bytes. Keep them generated from one source. -->

Expected outcome: the request's initiator becomes "Parser" or the preload link, and load delay drops to near the HTML arrival time — often a 500ms–1.5s improvement for client-rendered heroes.

2. Raise the priority

html
<img src="/img/hero-1200.avif" fetchpriority="high" …>
<!-- trade-off: fetchpriority=high on more than one or two images defeats the
     purpose — they compete with each other and with CSS. Use it only on the
     element you intend to be LCP. -->

Expected outcome: the image starts at "High" priority instead of being boosted after layout; typical savings are 100–400ms on congested connections.

3. Remove lazy loading from above-the-fold images

javascript
// Component-level rule: the first N images in the main content are eager.
function imgProps(index) {
  return index < 1
    ? { loading: 'eager', fetchpriority: 'high', decoding: 'async' }
    : { loading: 'lazy', decoding: 'async' };
}
// trade-off: "first N" is a heuristic. On layouts where the LCP image is not
// the first in source order (a side-by-side hero), mark it explicitly instead.

Expected outcome: no lazy attribute on the LCP image; fixing lazy-loaded images that delay LCP covers loader libraries that hide this.

4. Remove or warm the cross-origin connection

Serve hero images from the main origin through a CDN path rule (/img/* routed to the image service), or preconnect to the image host early in the head.

html
<link rel="preconnect" href="https://images.example-cdn.com" crossorigin>
<!-- trade-off: preconnect opens a socket whether or not it is used; an unused
     preconnect wastes a connection slot. Only preconnect to origins needed for
     above-the-fold content on THIS template. -->

Expected outcome: DNS, connect and TLS disappear from the image's timing on the first request, saving 100–300ms on mobile.

Load delay after each fix (mobile, Fast 4G) Bar chart of resource load delay falling from 1300 milliseconds to 160 milliseconds as each fix is applied cumulatively. Load delay after each fix (mobile, Fast 4G) Baseline (JS-rendered hero) 1300ms + img in server HTML 420ms + fetchpriority high 260ms + preconnect image host 160ms

Verification

In a fresh trace with the same throttling, the LCP image request should start within roughly 100–200ms of the HTML response's first byte, and the LCP insight should show load delay as the smallest of the four phases. Check that the request is initiated by the parser or preload, at High priority, with no connection setup in its timing.

In the field, the web-vitals attribution build reports resourceLoadDelay directly:

javascript
import { onLCP } from 'web-vitals/attribution';
onLCP(({ value, attribution }) => {
  report({ lcp: Math.round(value), loadDelay: Math.round(attribution.resourceLoadDelay),
           loadDuration: Math.round(attribution.resourceLoadDuration),
           url: attribution.url });
});
// trade-off: field load delay includes real network variance — a slow TTFB
// on one request does not inflate it, but contention from other tabs or
// background downloads can. Compare medians and p75s, not individual beacons.

Track the p75 of loadDelay as its own series; a regression there almost always means a template change hid the image from the preload scanner.

Framework Patterns That Reintroduce Load Delay

Image components that render a placeholder first. Some image components render a <div> with a blurred background on the server and swap in the real <img> after hydration. The browser cannot request the full image until hydration. Prefer components that emit the real <img> with a placeholder as CSS background on the same element.

Responsive images chosen in JavaScript. Code that reads window.innerWidth to pick an image URL cannot run before the preload scanner. Use srcset and sizes so the browser chooses.

A/B testing scripts that swap the hero. Client-side experimentation tools that replace the hero image after their script loads add the script's download and execution to load delay, and often create a second LCP candidate. Run hero experiments server-side or at the edge.

Streaming SSR with the hero in a late chunk. If the hero is inside a Suspense boundary that resolves late, its <img> arrives late in the stream. Keep the hero outside slow boundaries, or emit its preload in the shell — see flushing the document head early.

FAQ

How is load delay different from TTFB?

TTFB ends when the first byte of the HTML arrives. Load delay starts there and ends when the LCP resource request begins. A slow server inflates TTFB; a page that hides its hero from the preload scanner inflates load delay. They have entirely different fixes, which is why the phase breakdown matters.

Can 103 Early Hints reduce load delay below zero?

Effectively, yes: with Early Hints the server tells the browser to preload the hero before the final HTML response is ready, so the image request can start during server think time. Load delay as measured from the first byte of the final response may then be negative or zero. See using 103 Early Hints behind a CDN.