Cloudinary is the market leader in providing media asset management. They provide a wide ranging feature set from delivering highly optimised images to streaming video and even a DAM system. I’ve worked with Cloudinary a lot over the past few years and I have been given the privilege to become an MDE (Media Developer Expert). This the first official piece of content I’m creating as an MDE at Cloudinary.

In this instalment I’m focusing on lazy loading techniques for images that have been optimised and transformed by Cloudinary’s URL API. Cloudinary also provides client side and server side SDK’s for handling images. For the sake of simplicity I will stick to the URL based API.

Why Lazy Load?

Lazy loading is becoming increasingly important as websites get richer in content. I’ve been working on projects where product pages are basically a story in images and videos with a buy button. Globally the internet speeds for mobile devices have not increased as much as the explosion of media rich pages. Hence, we need to take care of our lovely users and not serve them unnecessary images. Do not load any images on page load to reduce initial http requests and make sure not to load images that are not visible on the screen. For example if you have a big mega menu with a bunch of images inside it, only load these when the user opens the menu. The same goes for horizontal image sliders and carousels.

Cloudinary do not provide any methods to lazy load assets but they do a whole lot of other things that you should really check out if you want to serve optimised images. In my opinion Cloudinary provides (among many others things) three essential things for serving high quality optimised images:

  • Smart quality optimisation (q_auto)
  • Serving of the correct file format for your browser (f_auto)
  • Rendering with the correct render method. (fl_progressive)

I add these values by default to every Cloudinary image URL. fl_progressive is a default value by the way. Click the links above to read more in depth articles about the features.

Things you need to do

Next to having proper placeholders for images that are not yet loaded (more about that later) the worst thing to do is not taking care of the space an image needs to load into. If you do not manage this in your CSS you will have a moving interface when images load and take their space. Imagine taking lazy loading into account (you are awesome for doing that) but then have a bad experience on page load where the whole interface shifts around when images load. The only thing you have accomplished is a bad initial page load experience. But wasn’t that the exact thing you wanted to optimise by implementing lazy images?

What you need to do is to make sure that the wrapper element around your image has the proper size for the image to load into. I know this is not that easy as you usually only know the image ratio rather than its exact size. We’re building responsive interfaces after all.

Use padding in percentages based on image width to take up the correct amount of space needed for the image based on its aspect ratio. If you have an element that is 200px wide with a padding-top of 100%, the padding-top would be 200px. This is a 1:1 aspect ratio. If you force the height of the element to zero then padding will be the only part of the box model affecting the height. If you use a padding of 56.25% you get a 16:9 ratio (9 / 16 = 0.5625). Awesome right?

The cleanest technique for this is to use a :pseudo element. Add this mixin to the wrapper around your image tag.

@mixin aspect-ratio($width, $height) {
  &:before { content:''; float: left; padding-bottom: percentage($height/$width); }
  &:after { content:''; display: table; clear: both; }

If you don’t like SCSS you can also do some modern fancy stuff like this (note that I’m not using the:afterin this example. CSS is flexible).

<div class="image-wrapper" style="--aspect-ratio:16/9;">
    alt="A dude saying hi"
[style*="--aspect-ratio"] > :first-child { width: 100%; }
[style*="--aspect-ratio"] > img { height: auto; }

@supports (--custom:property) {
  [style*="--aspect-ratio"] { position: relative; }
  [style*="--aspect-ratio"]::before {
    content: "";
    display: block;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
  [style*="--aspect-ratio"] > :first-child {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;

Another thing you need to do is implement a <noscript> tag with a fallback image in it for users without JS or for SEO spiders. I know Google parses JS, but at the time of writing they only parse the JS when they have resources and crawl your page a second time. If you have images with alt tags in your DOM, Google will index these on the first round trip.

Types of image tags

Image implementations come in different forms. If you just need to show an image without scaling it for responsive purposes, use a plain old image tag.

These examples have shortened Cloudinary URLs for readability purposes.

  alt="A dude saying hi"

If responsiveness is important, use the <img /> with srcset and sizes.

  alt="A dude saying hi" 
  srcset=",f_auto,w_320/hi.jpg 320w, 
,f_auto,w_600/hi.jpg 600w, 
,f_auto,w_850/hi.jpg 850w"

And if you need different image sources per media query you can use the<picture>tag. Make sure your biggest image source is placed first.

    media="(min-width: 650px)"
    srcset=",f_auto,w_640/hi.jpg 650w, 
  ,f_auto,w_700/hi.jpg 700w, 
  ,f_auto,w_850/hi.jpg 850w"
    media="(min-width: 400px)"
    srcset=",f_auto,w_300/wut.jpg 300, 
  ,f_auto,w_350/wut.jpg 350w, 
  ,f_auto,w_400/wut.jpg 400w"
    alt="A dude saying hi" 
    srcset=",f_auto,w_150/small.jpg 150w, 
  ,f_auto,w_250/small.jpg 250w, 
  ,f_auto,w_400/small.jpg 400w"

I haven’t mentioned background images here to keep the things simple.

Placeholder options

You have a bunch of options to indicate that nice crisp images are about to be shown. You could do nothing which results in a gap in your page that is filled as soon as the image is loaded. This is a viable solution that doesn’t require much work.

If you are into this approach I would suggest to take it a small step further by adding a background colour that has just enough contrast to indicate that the user is looking at a placeholder. Some websites use a gradient as a placeholder based on what colours are shown in the image. You could potentially add your logo or an animated loader with some CSS magic.

To make sure that some browsers do not show the “I cannot find this image” broken image error use a 1px transparent GIF in the src attribute.

  alt="Some image" />

Then there are some alternatives that are a little trickier to implement which give your implementation an extra ‘fanciness’ factor. Look atthisamazing SVG placeholder idea that has been around since 2017.

Alternatively you can use the “blur-up” technique. In this example the initial image that is loaded as placeholder is a low resolution and blurred version of the original image. Once the original image is loaded it replaces the blurry version for a nice “blur-up” effect. To make the blur more convincing you can also add it with a CSS filter.

The “blur-up” technique makes for a lovely looking experience but the drawback is that you have two http calls for a single image. In our projects we tend to make product pack shots blur-up and additional helper assets load with a subtle coloured placeholder. Choose your weapon with care.

Let’s look at a bunch of these techniques combined in a lovely code sandbox example. Note that Cloudinary is super fast. To see the techniques in action, throttle your connection in the dev-tools panel of your browser of choice.

Another note: applies the styling I added on the document ready event. If you throttle your connection you can see that very clearly. SSR would have been a good option here. I’ll talk more on the accessibility of slow connections and how to handle these gracefully in a future post.

And lastly: this code is meant as an example to make you understand what it means to create lazy loading images. For production use I would suggest using something like Lozad.js or lazyload.js.