Stop Lazy Loading Product and Hero Images
At 4/19/2024
I see a recurring performance problem on many ecommerce sites—the most important images on the page are being lazy loaded when they shouldn’t be. It impacts user experience, Core Web Vitals, and by extension search engine optimization. You’re better off not implementing lazy loading at all than implementing it incorrectly.
For example, a former client of ours asked us to look at the performance of their product page. They saw a significant drop in conversion after they launched a new design and wanted to know why.
Digging into the network waterfall chart, I noticed that the product image—the most important image on the page—took 16 seconds to appear on a fast 3G connection. It was the 60th asset downloaded.
I can’t talk about the client site in detail so I found another site, Mongoose Bikes, using the same Shopify theme. Let’s use their amazing, retro Supergoose bike as our example page. The video below captures how the page loads:
There’s a lot of performance issues here. The page takes 44 seconds to finish loading. But for now, let’s focus on the main product image which takes over 14 seconds to appear. It was the 69th asset downloaded.
This image is not only the most important image for customers, but it is also the image that will count towards the Largest Contentful Paint measure. Largest Contentful Paint is one of the Core Web Vitals that Google promotes and uses in its search engine page ranking algorithm.
How to tell if your images are lazy loading
If you’re not a technical person, don’t worry. I recorded a quick video showing you how to check if images on your site are lazy loading when they shouldn’t be.
Why does the image load so slowly?
One of the first things I do when investigating why an image is loading so late is to view the source of the page to find the code for the image. It is important to view the source of the page, not look at the current DOM in developer tools. We want to know what the browser can see when it downloads the initial HTML document. If we look at the DOM, we’re seeing what the browser knows after CSS and JavaScript have been processed.
This is the markup associated with the Supergoose’s main product image:
<img class="photo-zoom-link__initial lazyload"
data-src="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_{width}x.png?v=1632622682"
data-widths="[360, 540, 720, 900, 1080]"
data-aspectratio="1.5"
data-sizes="auto"
alt="Supergoose">
<noscript>
<img
class="photo-zoom-link__initial lazyloaded"
src="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_740x.png?v=1632622682"
alt="Supergoose">
</noscript>
Code language: HTML, XML (xml)
The page uses a JavaScript-based carousel to display multiple product images. There are four data
attributes and an entire noscript
block used to hide this image from the browser’s pre-loader so that the image isn’t download early and is instead lazy loaded.
But this is the main product image. It is the most important image on the page. This image shouldn’t be lazy loaded. If anything, we should consider preloading the image. It is that important.
Avoid animation for your most important images
The Supergoose page contains another mistake I commonly see coupled with lazy-loaded images. The main product photo has a one second fade-in animation applied to it.
Google’s Largest Content Paint guidance states:
To provide a good user experience, sites should strive to have Largest Contentful Paint of 2.5 seconds or less.
If you’re trying to pass Core Web Vitals to maintain your search ranking, you can’t afford to spend almost half of your LCP budget on animation.
Lazy loading every image does more harm than good
It seems like many developers have heard that lazy loading images is good for performance. They want to build fast sites so they lazy load all of the images.
But when you lazy load all of the images, you defeat the purpose of lazy loading. The benefit of lazy loading is telling the browser to defer images and other assets that aren’t immediately needed.
For example, someone may never scroll down far enough on a page to see an image located in the footer. If that footer image starts downloading immediately, it may delay assets that are more important. So lazy loading says to the browser, don’t download this footer image until it is needed. Typically, this means don’t download it until the user scrolls the page far enough that it seems likely the image will be seen.
By contrast, images at the top of the page—logos in the header, hero and product images—are immediately visible. They need to load as quickly as possible so the page feels complete and ready to be interacted with.
Browsers are pretty smart about prioritizing assets in their download queue. They read an HTML document from top to bottom and assume that images found higher in the document are more important and start downloading them sooner.
That is unless you lazy load all of the images. In that case, you’re hiding images from the browser and substituting your own knowledge of the loading order over that of the browser. If you lazy load images above the fold, you’re shooting yourself in the performance foot.
Stop using lazy loading libraries for most use cases
It used to be that in order to lazy load images, you needed to implement a JavaScript library. Thankfully, this is no longer the case.
All evergreen browsers now support the loading
attribute. You can specify loading="lazy"
on an image and get most of the behavior you want without all of the Javascript overhead. Here’s how the earlier markup would look using this web standard.
<img class="photo-zoom-link__initial"
src="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_540x.png?v=1632622682"
srcset="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_360x.png?v=1632622682 360w,
//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_540x.png?v=1632622682 540w,
//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_720x.png?v=1632622682 720w,
//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_900x.png?v=1632622682 900w,
//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_1080x.png?v=1632622682 1080w"
sizes="auto"
loading="lazy"
alt="Supergoose">
Code language: HTML, XML (xml)
There is a lot less code needed. There is no need to hide images in a noscript
tag because the images are never hidden from the browser’s pre-loader.
As we’ve already discussed, this image shouldn’t be lazy loaded at all. So the ideal markup wouldn’t include the loading="lazy"
attribute at all.
But one nice thing about using loading="lazy"
is that if you accidentally apply it to an image high up on the page, the browser will load it as soon as it builds the page layout and realizes the image is in the initial viewport.
That’s still an unnecessary delay, but the image will still load earlier than if you’ve used a JavaScript library to implement lazy loading. If you use a JavaScript library, the browser won’t know to download the image until that JavaScript library executes and that is typically much later.
Use lazy loading libraries for images in menus and carousels
The one use case where it continues to make sense to use lazy loading JavaScript libraries is for images in menus, carousels, or other images that are hidden. You can see this happening on the Supergoose page.
The first two images the browser downloads are a US flag and world icon that are only seen on small screens if you open the menu and scroll to the bottom:
While these icons are small in file size, they are images that many users will never see. They shouldn’t download ahead of images that are immediately visible in the viewport.
But while these icons may not be a big deal, the next images downloaded are more problematic.
These images are nowhere to be found when viewing the website on a mobile phone. It is only when you open the menu on a wide screen that you discover where these images are being used:
On small screens, these images shouldn’t be downloaded at all. On wide screens, they shouldn’t download ahead of more important images like the main product photo. These are images that we should lazy load.
Unfortunately, using loading="lazy"
won’t work for these images because it makes decisions on when to load images based on whether or not an image is in the current viewport. It doesn’t care if the images are hidden. In this case, the menu is in the initial viewport so the browser will begin downloading those images ahead of more important assets like your hero image.
This is where JavaScript libraries like lazysizes continue to shine. They can be used to ensure hidden images don’t download until they are in view. And because the images will never be viewed on small screens, they images will never be downloaded.
In short, use loading="lazy"
for images that scrolling will bring into view. Use JavaScript libraries for hidden images that you want to lazy load.
Summary
- It is better to not lazy load anything than to lazy load the wrong things.
- The goal with lazy loading is to delay the loading of assets further down on the page that users may not scroll to. The closer something is to the top of the page, the less likely it is to be something you want to lazy load.
- I know there is no fold because there is no consistent screen size, but in this case, we do care about the fold. We want items above the fold to load as quickly as possible. Instead of lazy loading, we may want to preload above the fold images.
- Our Largest Contentful Paint budget is only 2.5 seconds. Don’t waste time on animating images.
- Don’t use JavaScript libraries for lazy loading images (with a few exceptions). Instead use the new web standard of
loading="lazy"
.
Lazy loading is a key technique when it comes to increasing page performance, but like any technique, it shouldn’t be applied everywhere.