Troubleshooting guide · images · Published 2026-08-15 · 3 min read
CSS background images slowing down LCP
Fix LCP from CSS background images: preload with image-set, understand resolution switching, and know when to switch the hero to an img.
- ·Why background images lag
- ·Preload correctly
- ·When img beats background
The symptom
Lighthouse shows LCP above 2.5 seconds. The audit names the largest contentful paint element as a div with a CSS background. The image itself is heavy, but the surprising part is that it seems to wait much longer than an equivalent img.
Why background images bypass normal LCP handling
CSS background images travel a different path than img resources:
- They are not in the markup, so the browser cannot discover them until it parses the CSS and matches the element.
- They render by default after the layer is painted, at default fetch priority, well after the initial page-load burst of scripts and styles.
- The LCP detection still sees the painted layer, but the fetch got no priority boost, and there is no
loadingattribute to reason with.
Result: the hero is often the last big resource to finish, and LCP reports poor on a page that otherwise loads fast.
Preload the background image
You can pull the fetch forward with a <link rel="preload" as="image"> in the head. This is the same technique used in the hero image reference, applied to the URL the background uses:
<link rel="preload" as="image" href="/hero-mobile.webp" fetchpriority="high">
.hero {
background-image: url("/hero-mobile.webp");
background-size: cover;
}
The browser then discovers the hero the moment it parses the head, before the CSS lands. That fixes the discovery and priority lag.
Resolution switching and image-set()
An img gets srcset and sizes to pick a candidate by viewport. A background has no such mechanism, but modern browsers support image-set():
.hero {
background-image: image-set(
url("/hero-mobile.webp") 1x,
url("/hero-desktop.webp") 2x
);
}
image-set lets the browser choose a candidate by pixel ratio and, where supported, by format (type()). Note the trade-off: a preload link can only hint one URL, so pairing it with image-set is awkward. The reliable pattern is either (a) preload the single most-used image, or (b) skip preload and let image-set deliver the right format and candidate for the device.
When it is fine to accept the trade-off
Because preload plus a multi-candidate background doubles the surface area for mistakes, many teams switch the hero to an img with srcset, sizes, and fetchpriority. That is a legitimate design choice, not a failure, when your responsive behaviour is complex. It changes the LCP element from a background layer to a normal image, making it one of the first payloads again.
If a full switch is too invasive, at minimum:
- Confirm which candidate the browser actually fetches for the common mobile viewport.
- Preload that exact URL in the head.
- Set
fetchpriority="high"on the preload. - Re-run Lighthouse and the lab / field comparison.
Ordered fix steps
- Read the "Largest Contentful Paint element" audit and note the background URL.
- Add the matching
preloadlink to the head withfetchpriority="high". - If the CSS uses multiple candidates, either preload the one the majority of visitors use, or convert to
img. - Confirm the preloaded URL matches the served file exactly (extension, variant), otherwise the preload is wasted.
- Re-test LCP, and compare against the field report, because a desktop lab can hide a mobile-only gap.
The LCP optimisation guide walks the remaining legs (server time, render blocking), and the layout shift guide keeps the hero's slot reserved so the faster paint does not bring shift along.