Background images are evil

Why background images hurt your Core Web Vitals and how to replace them

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-09

Background images are evil

Those of you that know me or have heard me speak might have heard me talk about background images. Those of you who have not: I really really do not like background images. Here is why I don't like background images, how to quickly find pages with background images and how to fix them.

Last reviewed by Arjen Karel on March 2026

Why background images are bad for the Core Web Vitals

When loading a web-page every element has its time and its place. With some modern techniques like deferring, preloading, async loading, scheduling, defining the fetchpriority etc etc we can get all critical resources pretty much under control. Except for background images! 

Consider this real-world example:

On a daily basis, mostly in WordPress sites I see this pattern. All the normal images are lazy loaded and some images (in this case the social icons in the footer) are background images. Can you guess what happens?

<html>
<head>
    <style>
        footer {
            /* a margin of 100 vh wil make the footer off-screen !*/
            margin-top: 100vh;
            >.social {
                >.facebook {background-image: url('img/facebook.jpg');}
                >.instagram {background-image: url('img/instagram.jpg');}
                >.linkedin {background-image: url('img/linkedin.jpg');}
                >.email {background-image: url('img/email.jpg');}
            }
        }
    </style>
</head>
<body>
    <!-- yes this image is lazy loaded, because tiny mistakes happen! -->
    <img loading="lazy" src="img/hero.jpg"></img>
    <footer>
        <div class="social">
            <span class="facebook"></span>            <span class="instagram"></span>            <span class="linkedin"></span>            <span class="email"></span>        </div>
    </footer>
</body>
</html>

You might have guessed it: the offscreen background images are queued for download before the far more important 'hero.jpg' image which will usually become the largest contentful paint element on the page.

lazy lcp with bg img

But that is not all!

As I said background images are evil! That is because, aside from the weird priority that they sometimes get, background images lack the cool features that normal images get!

  • No lazy loading: there is no loading attribute for background images. The loading="lazy" attribute that works on <img> tags (with 94.9% global support) simply does not exist for backgrounds.
  • No async decoding: there is no decoding attribute for background images
  • No fetchpriority: there is no fetchpriority attribute for background images. You cannot tell the browser which background image matters most. With <img> tags, 17.3% of mobile pages already use fetchpriority="high" on their LCP image according to the 2025 Web Almanac.
  • Responsive image sources: The image-set() function for background images does not have the same features that you get with srcset and the <picture> element.
  • No width and height attribute. Not being able to set the simple width and height attribute means the browser cannot reserve space for the image, which causes layout shift (CLS). You end up using CSS workarounds, and more code is always slower than less code with the same complexity!
  • No alt text: background images have no alt attribute, which hurts accessibility and means Google Images cannot index them.

Background images loaded via url() are valid LCP candidates. A slow background image will show up as your LCP, but you have none of the tools above to optimize it. The browser must first download and parse the CSS before it even knows the image exists. This resource load delay adds roughly 400ms to the median LCP according to Google's own measurements.

According to the 2025 Web Almanac, 9% of mobile pages still use a CSS-initiated image as their LCP element. That number has not changed since 2022. On sites monitored by CoreDash, replacing a hero background image with an <img> tag improved the median LCP by 35%.

Quickly find all background images on a page

So how do we find out if a webpage has background images on them? Well you could check out the network inspector, filter on images, right click the menu bar, enable the initiator columns and check the initiator but it is far easier to paste this code into the dev console.

Simply open the dev console with Ctrl-Shift-I, navigate to the console tab and paste this code. It will show you all the background images on the page.
let bgimg = performance.getEntriesByType('resource')
  .filter(rs => rs.initiatorType == 'css')
  .map(rs => {
  return {
    name: rs.name,
    initiator: rs.initiatorType
  }
}) || [];

(bgimg.length > 0)?
    console.table(bgimg):
    console.log('No background images on this page!');

This will show you a cleanly formatted table with all the background images names and the initiators.

background img console table

How to avoid background images

Background images are easily avoidable. How to do this depends on the image itself. There are roughly 2 methods.

In case of 'normal images'

You would not believe it if I told you but the majority of the cases where I find background images the background part of the image does not even have a purpose. The images just 'need to be somewhere on the page' and the background-image:url() is misused for this purpose.
If this is the case just add a normal image tag and remove the background image from the stylesheet.

In case of cover images:

Cover images are images that completely cover a parent container. Using background images as cover images sort of makes sense because a long time ago this used to be the only way to get cover images and I guess people just stick to what they know. Fortunately there are better options available to us. So let's fix it!
In case of cover images just remove the style   background-image: url(hero.jpg); background-size: cover; and place a normal image in that same container and edit the CSS to look like this:

<style>
.img-container {
    position: relative;
    > img {
       width: 100%;
       height: 100%;
       object-fit: cover;
       position: absolute;
       z-index: 1;
   }
}
</style>

<div class="img-container">
  <img
       height="500"
       width="300"
       decoding="async"
       loading="lazy"
       src="hero.jpg"
       srcset="hero-320w.jpg, hero-480w.jpg 1.5x"
       alt="alt text"
       fetchpriority="low"
  >
</div>

Now you have a proper image with width, height, loading, decoding, srcset, fetchpriority and alt attributes. Everything the browser needs to load it efficiently.

When you must keep a background image

Sometimes you do need a CSS background image: repeating patterns, decorative overlays, or cases where the CMS gives you no other option. In those situations, preload the image so the browser discovers it early:

<link rel="preload" href="hero.webp" as="image" type="image/webp" fetchpriority="high">

Place this as early as possible in the <head>. For offscreen background images, you can defer them with an IntersectionObserver so they only load when the user scrolls near them.

To verify your changes actually improve real user experience, set up Real User Monitoring. Lab scores tell you what should be faster. Field data from real visitors tells you what actually is.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Pinpoint the route, device, and connection that fails.

CoreDash segments every metric by route, device class, browser, and connection type. Real time data. Not the 28 day average Google gives you.

Explore Segmentation
Background images are evilCore Web Vitals Background images are evil