First: decoration or content?
The most important decision comes before any optimization: is the image decorative (texture, pattern, gradient behind text) or content (the product, the subject, what it's about)?
- Decorative → CSS
background-imageis right. No alt text needed, not relevant to image search. - Content →
<img>with alt text. It's part of the content, should be found, and can be served responsively via srcset.
A common mistake is setting a content hero photo as a CSS background — then the alt text is missing, it's not in image search, and it's harder to speed up.
The size problem
The classic performance killer: a single 2500 px background image served to everyone — including the 400 px phone. On top of that, the browser often discovers CSS background images late, because it has to render the element first before requesting the image. A large hero background image thus quickly becomes the LCP element and dominates the measured load time.
Solution 1: per-device sizes via media query
Instead of one giant image for all: give smaller screens a smaller image.
.hero {
background-image: url("hero-800.jpg");
}
@media (min-width: 800px) {
.hero { background-image: url("hero-1600.jpg"); }
}
@media (min-width: 1600px) {
.hero { background-image: url("hero-2400.jpg"); }
}The phone only loads the 800 px variant, the large monitor the 2400 px version. You create the variants as usual with the resize tool and compress them — as WebP they're smaller still.
Solution 2: modern formats with image-set()
With image-set() you offer the browser several formats, and it takes the best one it understands — WebP for modern browsers, JPG as a fallback:
.hero {
background-image: image-set(
"hero.webp" type("image/webp"),
"hero.jpg" type("image/jpeg")
);
}Solution 3: for the important hero image, prefer an img
If the hero background is the central, large image of the page, a real <img> is often the better choice: it can be preloaded (the browser loads it early) and served responsively via srcset — both noticeably improve the LCP. Place the text over it with an absolutely positioned overlay. How to speed up the LCP image specifically is in Preloading the LCP image.
Further details
- background-size: cover fills the container without distortion — standard for hero areas.
- Texture/pattern as a small tileable file with
background-repeat— tiny instead of huge. - Gradients ideally as a CSS
gradientinstead of an image — zero extra load bytes. - Fallback color set (
background-color) so the area already looks sensible before the image.
Frequently asked questions
What's the difference between background-image and an img element?
background-image is purely decorative and set via CSS; it doesn't appear in the HTML content and has no alt text. An img element is a content part of the page, can be described (alt), appears in image search, and can be served responsively via srcset. Content images belong in an img, pure decoration in the background.
Why does a CSS background image slow the page?
Because the browser often discovers CSS background images late (only when it renders the associated element) and because many simply load one huge image for all devices. A large hero background image can thus be the LCP element and dominate the load time.
How do I serve a smaller background image for phones?
With media queries: in an @media rule for small screens a smaller image is set as background-image, for large screens a bigger one. That way the phone doesn't load the desktop image. Modern formats can additionally be included via image-set().
Should the hero image be a background image or an img?
For an important, large hero image an img element is often better: it can be preloaded and served responsively via srcset, which improves the LCP. Text over it is then placed with a CSS overlay. Pure texture or pattern backgrounds stay CSS.