Blog Performance 9 min read

LCP Optimization Strategies When Hero Images Fail to Load

Prevent poor LCP scores when hero images fail or load slowly using fetchpriority, rel=preload, and a fast-loading fallback URL as the LCP candidate.

LCP image optimizationLargest Contentful PaintHero image performancefetchpriorityCore Web Vitals
LCP Optimization Strategies When Hero Images Fail to Load

The Largest Contentful Paint element on most pages is the hero image. When that image fails to load — due to a 404, a slow CDN response, or a null src — the browser must find another LCP candidate. The fallback candidate is often text, which typically appears much later in the render timeline, resulting in a poor LCP score above 2.5 seconds.

Defending your LCP against hero image failures requires two things: a fast, reliable fallback that serves as an acceptable LCP candidate, and correct priority signals so the browser fetches the hero aggressively. A generated placeholder URL served from a CDN with correct Cache-Control headers can function as the LCP element when the primary image is unavailable.

LCP and the hero image

Why the hero image dominates your LCP score

LCP is measured from navigation start to when the browser paints the largest visible content element. Above-the-fold images are almost always the LCP element because text, buttons, and other content are smaller. A hero image covering 60–80% of the viewport at load time will be the LCP candidate on most page views.

When the hero image is not painted — because the src is null, the network request fails, the image returns 404, or the server is slow — the browser falls back to the next-largest painted element. That is usually a text heading or a content section below the hero. Text renders at a different time in the loading timeline than images, often shifting the LCP timestamp significantly.

LCP above 2.5 seconds is rated 'Needs Improvement'. Above 4 seconds is 'Poor'. Both thresholds affect Core Web Vitals assessments in Google Search Console and the Chrome User Experience Report (CrUX), which reflect real user experiences rather than lab data.

fetchpriority

Signal the hero image priority to the browser

By default, the browser discovers images during HTML parsing and assigns them a fetch priority based on whether they are in the viewport. Above-the-fold images typically receive 'High' priority, but the browser does not know they are above the fold until after it has parsed enough of the document to calculate layout. This can delay the priority assignment.

The `fetchpriority="high"` attribute on the hero image tells the browser to fetch it at high priority immediately, without waiting for layout calculation. Combine this with `loading="eager"` to ensure the image is not deferred. Add a `<link rel="preload" as="image">` tag in the `<head>` for the most aggressive preloading.

Implementation text
<!-- Hero image with explicit fetch priority -->
<img
  src="https://example.com/hero.jpg"
  fetchpriority="high"
  loading="eager"
  decoding="async"
  width="1200"
  height="600"
  alt="Product hero"
/>

<!-- Preload in <head> for even earlier discovery -->
<link
  rel="preload"
  as="image"
  href="https://example.com/hero.jpg"
  imagesrcset="https://example.com/hero-800.jpg 800w,
               https://example.com/hero-1200.jpg 1200w"
  imagesizes="100vw"
/>

Fallback URL as LCP candidate

Use a fast fallback URL when the real hero fails

When a hero image src is null, undefined, or returns a 404, the browser cannot paint the LCP element. Providing a fast-loading fallback URL as the initial src — while the real image resolves — gives the browser a valid LCP candidate to paint on schedule.

A generated SVG placeholder from fallback.pics is served from Cloudflare's CDN. The response is fast (sub-50ms from most locations), cacheable, and does not require any file storage. As the LCP candidate, it paints quickly and keeps the LCP metric on track even when the real image is unavailable.

This is different from a loading state placeholder. For LCP purposes, you want the fallback to be a valid, visible element that the browser can commit to painting. An SVG with correct dimensions and a label is that element. The tradeoff: if the real image loads quickly, there may be a brief flash of the placeholder before the real image replaces it. For LCP optimization, this tradeoff is acceptable.

Implementation tsx
// Next.js: hero with fallback and fetchpriority
import Image from 'next/image';

const FALLBACK_HERO = 'https://fallback.pics/api/v1/1200x600?text=Loading';

export function HeroImage({ src, alt }) {
  const [imgSrc, setImgSrc] = React.useState(src || FALLBACK_HERO);

  return (
    <Image
      src={imgSrc}
      alt={alt}
      width={1200}
      height={600}
      priority // Next.js equivalent of fetchpriority=high + preload
      onError={() => setImgSrc(FALLBACK_HERO)}
    />
  );
}

onerror and null src

Handling null src and network errors separately

Two distinct failure modes need separate handling: a null or undefined src (the image was never set), and a network error on a valid src (CDN outage, deleted asset, 404 response). A null src renders no request at all — the browser paints nothing, and the LCP element is not the hero. A failed network request fires the onerror event, which you can intercept.

For null src, substitute the fallback URL before the component renders. For failed network requests, use an onerror handler. Both cases require setting onerror = null after the first fallback to prevent an infinite retry loop if the fallback URL itself returns an error.

Implementation tsx
// Vanilla JS: handles both null src and network failures
function mountHeroImage(containerEl, src, alt) {
  const fallback = 'https://fallback.pics/api/v1/1200x600?text=Hero+Image';
  const img = document.createElement('img');
  img.src = src || fallback;
  img.alt = alt;
  img.width = 1200;
  img.height = 600;
  img.fetchPriority = 'high';
  img.loading = 'eager';
  img.onerror = function () {
    this.src = fallback;
    this.onerror = null; // prevent infinite loop
  };
  containerEl.appendChild(img);
}

CMS and API sources

Hero images from CMS APIs are high-risk LCP candidates

When a hero image URL comes from a CMS API response — Contentful, Sanity, Strapi, or a headless Shopify storefront — the URL depends on both the API call succeeding and the image field being populated. Both can fail independently of each other.

Pre-compute a fallback URL during the data-fetching phase, before the component renders. This way the component always has a valid src from the first render. You avoid the double-render (first with null, then with the real URL) that causes a layout shift and a delayed LCP paint.

Measurement

Verify LCP improvements with Lighthouse and WebPageTest

Run Lighthouse before and after your hero image changes to verify LCP improvement. PageSpeed Insights shows both lab data (simulated) and field data (from CrUX). Field data reflects real user experiences and updates over a rolling 28-day window, so changes take time to appear there.

WebPageTest's filmstrip view is useful for identifying exactly when the LCP element paints. You can see whether the hero image, the fallback, or a text element is the LCP candidate at each step of the waterfall. This is more diagnostic than a single LCP number.

Implementation text
# Lighthouse CLI for LCP measurement
npx lighthouse https://yoursite.com --only-audits=largest-contentful-paint,render-blocking-resources,uses-optimized-images

# Check which element is the LCP candidate in DevTools:
# Performance panel > LCP marker > select event > Initiator shows the element

Resources

Further reading

The CLS missing images guide covers the layout-shift side of Core Web Vitals. The lazy loading guide covers below-the-fold image performance. For hero image placeholder aesthetics, see the LQIP and blur-up guide.

Implementation text
https://fallback.pics/docs/
https://fallback.pics/placeholder-image-api/
https://fallback.pics/blog/core-web-vitals-cls-missing-images/
https://fallback.pics/blog/prevent-layout-shift-missing-images/

Key takeaways

What to standardize before shipping

  • The hero image is the LCP element on most pages — a failed hero shifts the LCP candidate to text, often doubling the LCP time.
  • Add fetchpriority='high' and loading='eager' to hero images, plus a <link rel=preload> in <head> for the most aggressive loading.
  • Use a fast-loading fallback URL as the initial src when the hero image src is null to keep the LCP element valid from first render.
  • Handle null src and network errors separately: substitute the fallback before render for null, and use onerror for network failures.
  • Measure LCP improvements with Lighthouse and WebPageTest filmstrip before deploying — field data via CrUX takes 28 days to update.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs