How to optimize images for web?

Images are usually the heaviest assets on a website. As such, if not optimized properly, they can have a negative impact on the performance and thus the user experience of your website.

According to The Web Almanac the median page size on mobile has been just under 2MB in 2021, with around half of the downloaded content being attributed to images. That's why optimizing images for web can be crucial to the performance of your website — keep in mind, mobile data are expensive, and not everyone has access to a fast internet connection, and the latest device on the market.

In this post, we'll deep-dive into the image optimizations and best-practices that are crucial for every single website:

Provide appropriately sized image variants

Serving large images on smaller screens not only requires more data to be downloaded, but downscaling the image to fit the viewport also comes with a cost in terms of resource consumption (CPU/GPU and memory). This means that when you're not serving appropriately sized images to you site visitors, you are wasting their mobile data, and you also unnecessarily increase the power consumption of their device. Providing too large images can also cause lags on your website when a user tries to scroll it.

Remember, an image twice the size will result in an image that will take about three to four times the data volume to be downloaded (since it has four times as many pixels). That's why it's a good idea to generate at least a few differently sized images and serve an appropriate version based on the devices' resolution and pixel density.

Ideally, you'll want to generate a version of an image for every breakpoint ranging from 320px to 2560px with a 200px to 400px steps. In reality, this approach could take too much of storage space on your server, so just choose a range that fits your needs.

The least you can do is generating resized image versions for the following breakpoints (keeping retina/high pixel density screens in mind, so for every breakpoint you'll also need a variant twice the size):

  • Mobile phones (768px and lower)

  • Tablets (between 768px and 991px)

  • Smaller computer screens (between 992px and 1399px)

  • Large computer screens (everything above 1400px)

The exact values of the breakpoints serve just as a general guideline, always adjust them to fit your needs and the actual maximum size of an image in the viewport.

Generating image versions can be done manually using a picture editor software such as Photoshop or Gimp. You can also use an online service, for example Adobe Image Resizer or ILoveImage. Nevertheless, if you have a lot of images on your website, you might want to use an image CDN or a CMS such as Phaistos.io that automatically resizes the images for you, or you can create an automated process within your build pipeline (e.g. by using the gulp-jimp package).

Once you have resized all your images, you can then simply add the srcset and sizes attributes to your image tags to load an appropriately sized image based on the viewport:

<img sizes="(min-width: 1200px) 1024px,
             (max-width: 1199px) 512px"

     srcset="/img/image-lg.jpg 1024w 1x,
             /img/[email protected] 1024w 2x,
             /img/image-sm.jpg 512w 1x,
             /img/[email protected] 512w 2x"

     src="/img/image-lg.jpg"

     alt="Responsive Image">

In the example above we are instructing the browser to load an image based on a specific breakpoint — image-lg.jpg with the width of 1024px will be loaded if the width of the viewport is equal or greater than 1200px, the image-sm.jpg with the width of 512px image will be loaded on all smaller screens. With the 2x modifier we instruct the browser to load the [email protected] or the [email protected] on all devices with a pixel density greater or equal to 2.

You can also leave out the sizes attribute, and let the browser decide what image to render. The src attribute in the example above serves as a fallback for all browsers that do not support the srcset attribute.

Compress your images

Resizing an image will always save you a significant portion of the image bytes, but only resizing the images usually isn't enough. The human eye is not perfect, and that's where lossy compression algorithms come into play — they take an advantage of imperfections in the visual perception of the human eye and can use it to significantly decrease the size of an image. Also, images on the web usually don't have to be perfect.

That's why you should always make sure your images are properly compressed. Nevertheless, a lossy compression is always a trade-off between the visual quality and the size of an image, thus make sure not to overdo it. With most of the image compression tools you can specify the desired compression factor, so just play around and find out what settings work the best for you.

You can compress your images by using software like Adobe Photoshop and Gimp, online optimization services like Optimizilla, adding image compression into your build pipeline (e.g. with imagemin), or you can use a CMS like Phaistos.io that automatically compresses all images for you. Also minify all SVG images — this can save you additional bytes while not compromising the image quality.

Use modern image formats

In the past years, new image formats such as webp or avif were developed. Those were designed to work best in the context of the web, and usually offer a better image quality with a smaller image size than the standard image formats like jpg or png.

The webp and avif image formats are supported in most of the modern web browsers (webp is supported by 97% and avif by 71% browsers relative to their global usage). So, if you can, always make sure to provide those two image formats with a fallback to a broadly supported image format like jpg or png.

With webp you'll save on average between 25% and 34% of the image bytes, and avif will usually save you about 50% of the image bytes.

Here is a code snippet to get you started:

<picture>
   <source srcset="/img/image.avif" type="image/avif">
   <source srcset="/img/image.webp" type="image/webp">
   <img src="/img/image.jpg">
</picture>

The code above will tell the browser to load the avif image if it's supported, otherwise a webp image shall be loaded. If neither of the image formats are supported, a jpg version of the image will be loaded.

To create avif and webp versions of your image, you can use one of the online conversion services such as Cloudconvert or Avif.io, build the conversion into your build pipeline (e.g. with the webp-converter Node.js package), or you can use a CMS that will automatically create image versions in modern formats for you.

When talking about image formats, we also need to mention GIF animations. GIF is an old format that usually adds a lot of unnecessary bytes to your animations. Webm and mp4 as a fallback are much better suitable for use on the web, so always convert your GIF animations to webm and mp4.

<video preload="metadata" autoplay muted loop playsinline poster="/img/poster.avif">
   <source src="/img/animation.webm" type="video/webm">
   <source src="/img/animation.mp4" type="video/mp4">
</video>

Lazy loading

The last piece of the puzzle in terms of image optimization is lazy loading. You should always make sure that images that are not critical (typically all images below the fold) are not loaded and rendered on the initial pageload. Instead, load them just before they are needed to save some download bandwith and also browser's rendering resources.

Native lazy loading is supported by most of the modern browsers, you just need to add the loading="lazy" attribute to your images. Also remember to set the height and width attributes to provide an aspect ratio to prevent unexpected layout shifts (the browser will reserve space in the viewport and will replace it with the image once it's loaded).

<img src="/img/image.png" loading="lazy" width="300" height="200">

If you need to support lazy loading in older browsers, you can use one of the available libraries like MiniLazyLoad, customize our boilerplate lazy loading code to fit your needs, or use a CMS like Phaistos.io that lazy loads non-critical images for you.

One last thing to remember when rendering images is that most browsers will download an image even if it or its parent is set to display: none; — so always remember to use proper lazy loading techniques to avoid downloading unnecessary images and wasting mobile data of your site visitors.

Conclusion

Images on your website can have a huge impact on its performance, that's why you should always provide appropriately sized and compressed images in modern image formats, and lazy-load all non-critical images below the fold.