Images are hard. Is there a simpler way?

5 min read

cover riffs on picture icon and shows long path over hills and short path on ground

Using images on the web is deceptively complicated. To give simple, concise advice on the best way to use images on the web is tricky. We all would like to avoid creating bulky, slow webpages but it is a battle sometimes. Images are at the forefront of that battle, because they typically make up 50% of a page’s weight.

The best coverage of images I read is a 10-part series by Jason Grigsby from 2015 called Responsive Images 101, which provided a thorough tutorial on responsive images, it is dated in some respects but stil worth a read. Also, Jason wrote an article in 2013 called 8 Guidelines and 1 Rule for Responsive Images that provides some guidelines. I haven’t seen a worthy, up-to-date predecessor to either of these articles. Even MDN’s guide to responsive images defers to Jason’s 2015 article on background images and media queries!

Putting images on the web is not just about making them responsive, there are other design and implementation considerations. There have been new developments in responsive design, which has led to some people heralding the beginning of the ‘new responsive era’. I wouldn’t get too caught up in that, but maybe it is a time to reassess.

The biggest issue for me right now is that things are convoluted, and we should be looking for a simpler way. There isn’t a good set of guidelines for dealing with images in a holistic manner. We are reaching for tools to whisk some of the complication away instead.

Today, I’d like to discuss some of these issues, and provide some of that missing guidance.

Where doth the difficulty lie?

There are a lot of different considerations with images. You bump into a heap of questions when you’re using images such as:

Chris Coyier also discussed this topic in his recent article Images are hard and said:

Putting images on websites is incredibly simple, yes? Actually, yes, it is. You use <img> and link it to a valid source in the src attribute and you’re done. Except that there are (counts fingers) 927 things you could (and some you really should) do that often go overlooked.

Chris goes on to list approximately 20 of these things. There are some bits that come to my mind also that Chris did not mention. I’m not going to make a complete list. It’s too much.

It makes me sad that things are like this. There have been improvements in some areas, better tooling is available to cover some of it, but some of it is impractical. I imagine it would feel overwhelming for someone who wants to build a website who is not a web developer but would like to; or a beginner who wants to do things the “right way”.

We should reassure people that it is OK to just use an <img> with one appropriately sized image when you are starting out. Your website will work fine. You are breaking no laws. You can learn more and improve on this later. As I discuss later, many of us are pretty loose with how we use images anyway, so he who has not sinned should cast the first stone!

In particular, I say this because it seems to me that that there is more pressure to make everything fast now. Web performance is being hyped up. People are talking more about Lighthouse scores. The marketing guys are linking improvements in performance to revenue gains. And there is the spectre of SEO-reaper lurking. 👻 SEO rankings are inexplicably being linked to performance now through Google’s core web vital metrics.

But, but…I just want to build a small, fan site for BTS! 😢 Maybe that’s why some people prefer to build Discord bots instead of websites now.

For me, there isn’t a cohesive and clear narrative on how to do things well. It’s an accumulation of stuff that is not synthesized well. I think it’s important to look at what people are actually doing, rather than what people are saying or suggesting to cut through the rhetoric.

Rereading Jason’s article on guidelines from 2013, he says:

No one knows what the future of responsive images holds. A few years from now, we will probably look back on the hacks we’re using and laugh at our naivety.

What do you think? 🤔

I’m still waiting for holographic personal devices. Where’s the 3D CSS?

How are images really used on the web

Let’s look at the Web Almanac to see what is really happening. The Web Almanac is an annual report which “combines the raw stats and trends of the HTTP Archive with the expertise of the web community.” Let’s look at last year’s report with regard to images and performance.

In 2020, the median page weighted approximately 2 MB for desktop and 1.9 MB for mobile, and a shocking 7.4 MB for desktop and 6.7 MB for mobile at the 90th percentile.

page weight distribution bar chart

The median page weight contains approxmiately 1 MB of images and approxmiately 400 KB of JavaScript.

show the weight content types on a bar chart

This is why people point at images as the lowest hanging fruit for reducing page weight. Images are still the most requested type of resource, though JavaScript is closing in!

The thing is it’s not just the size of the resources. It’s the amount of requested resources. This is the distribution of requests by content type.

show distribution of request types on a barchart

The median webpage has 29 images. This was more than I was expecting. The number of requests on desktop for the media page is the same as last year (74), yet the page weight has increased by 122 KB. Pages are getting bigger.

In terms of performance, it is hard to summarise. This is the most succinct summary directly from the report:

On the surface, we have seen optimistic signals about the new Core Web Vitals performance metrics. At least half of the experiences are good across both desktop and mobile devices, if we don’t narrow down to consistently poor experiences on slower networks for Largest Contentful Paint. While the newer metrics might suggest that there’s an ongoing uptake in addressing performance issues, the lack of significant improvements in First Contentful Paint and Time to First Byte is sobering. Here the same network types are most disadvantaged as with Largest Contentful Paint, as well as fast connections and desktop devices. The Performance Score also portrays a decline in speed (or perhaps, a more accurate portrayal than what we measured in the past).

What the data shows us, is that we must keep investing in improving performance for scenarios (such as slower connectivity) that we often don’t experience due to multiple aspects of our privilege (middle to high-income countries, high pay and new, capable devices). It also highlights that there’s still plenty of work to be done in the areas of speeding up initial paints (LCP and FCP) and asset delivery (TTFB).

Let’s just say there is room for improvement! 😉

What would you recommend?

Do the basic things well.

1) Ship less images!

Don’t serve 29 images for your average webpage unless you have good reason to. This article has 8 images. Show restraint, be selective.

This made me think of this quote from Dickens:

Subdue your appetites, my dears, and you’ve conquered human nature.

2) Inline SVG icons

Pasting SVG icons into your HTML will reduce the amount of requests. In the age of static site generators and web components, it is easier to do this in one place and avoid duplication.

3) Use a rule-of-thumb to set the maximum image resolution for your images

You can pick the maximum image size for your page based on its content. For example, if you have a blog and the maximum width of your blog post is 700px, then serve images with a maximum width of 1400px. This will cover high-resolution screens with 2x pixel density. Jake Archibald mentions this as the “lazy way” in his article Halve the size of images by optimising for high density displays. I think it’s the smart way.

show example of blog post with width on desktop and highlight max width of image

4) Provide alternative text

For the sake of accessibility, give a text alternative for an image in the alt attribute. You should add alt text to most images, unless it is considered decorative. Eric Bailey discusses when you can skip alternative text in the article - Your Image Is Probably Not Decorative. Spoiler altert: it’s not that often.

5) Stick to using one image format with img if you are inexperienced

For a beginner, I would suggest sticking with a single image format with img. You don’t need to learn a static site generator or JavaScript to get started making websites (but it will make it easier in the longer-run). Use JPEG for photos, and use either a PNG or SVG (preferably) for more graphical images such as icons and diagrams. Maybe next year, you can start using WEBP instead of JPEG and PNG. At the moment, WEBP is only partially supported in Safari.

If I follow this recommendation for this page, I use 8 images in the article totaling 169.5 KB. I did run 2 images through squoosh to reduce their size. That’s already quite good.

6) Set dimensions for images to reduce layout shifts

Define the dimensions of images through the width and height attributes, or in CSS with some combination of aspect-ratio, width, and height. This will prevent parts of your page moving around when an image loads. This is called cumulative layout shift (CLS) and is one of Google’s core web vitals metrics.

I would prefer to use aspect-ratio and width for some images, but aspect-ratio has only just made it to Safari. So, now it is in all major browsers, just keep this in mind if browser support is an issue for you.

If you want to learn more about the ins-and-outs of this, you can read the article - Setting Height And Width On Images Is Important Again. It’s a 17 min read, yowzer! 🤓

7) Lazy load images below the fold

For any images below the fold, by below the fold I mean any image that would not be seen when the page is loaded, you can lazy load these images. This will defer loading of images that are off-screen until the user scrolls near them . You can do this by adding this attribute loading="lazy" to <img>.

This attribute is supported in 74.71% of browsers now, but looking at the most recent data (Jan - Jun 2021) on the HTTP Archive, it is used by just 17% of websites. If I add lazy-loading to this page, only the the main image needs to be loaded, that’s just 2.1 KB.

8) Learn the basics of SVG and use SVG for graphical images as often as you can

I would recommend that you learn the basics of SVG. Using SVG instead of PNG is an easy win. Since SVG is a text-based vector format, files can be very small. For example, the main image for this article is a SVG and is 2.1 KB. SVGs can scale up and down without any loss of quality. Effectively, you can have 1 image that looks good on any screen.

demonstrating visaul difference between raster and vector images scaled up
By Yug et al. from Wikimedia Commons CC BY-SA 2.5

9) Optimize your images

The goal here is to produce a smaller file size without a perceptible degradation of the appearance of the image.

You can use web apps like tinypng for bulk optimization of PNG and JPGs, squoosh for individaul optimization for many formats, and SVGOMG for optimizing SVGs. If you are comfortable with using command-line tools, you can try imagemagick to do anything imaginable.

As I mentioned, I would only to need to optimize 2 PNGs for this post. The rest were not that big or wide. In any case, I can drop them all into tinypng and get a fully optimized set in a zip. If you don’t use a lot of images, then this is a quick process!

We will discuss automation of this process in the next section.

If you followed the steps above, you will be ahead of the median website. Test your website in your favourite auditing tool (such as Webpagetest or Lighthouse) to see where you’re at.

I followed the steps above for this post and got 99/100 for performance in PageSpeed Insights, check it out for yourself: When loaded for the first time, the initial page weight is 82.2 KB, it is 5.2 KB when requested a second time (with caching). This score reflects other implementation decisions I made, but you can see that images ain’t an issue.

Finally, look at what browsers you need to support to see if you need to polyfill the loading attribute.

Beyond this, you are getting into more technical territory, but through automation you can take some of the work off your plate.

The extra yards and automation

The next biggest win is to use next-gen formats for browsers that support them. Using WEBP over JPEG can yield a 30% saving in file size, and the brand new AVIF format offers even greater savings but browser support is limited.

Along with using next-gen formats, you can use different resolutions of an image for different screen sizes (resolution switching). These savings are hard to generalise, they depend on your page design and your users devices.

If you use both of these methods, you can easily justify having 6 versions of the same image to cater for everyone.

Jake Archibald looked at some data from and found:

Matt Hobbs from was kind enough to share April 2021 stats on users’ device pixel ratio (DPR). I ran a few queries on it, and it turns out 80% of their users are on a screen with a DPR of 1.5 or above. That’s:

…over 99.9% of their mobile users. …32% of their desktop users. …78% of their tablet users.

Data can inform your decisions as you grow your website. For, they don’t need to serve smaller images close to the 400px, mobile users benefit from images closer to 600px. They could get away with 1 less image. This example is just for the UK, but it illustrates an overlooked point that you may be optimizing for something that is not needed, or yields little benefit. At the same time, we should not neglect users with older devices or slower connection speed because they are in a minority. This is the hard balance to strike.

You can choose to do this yourself or use an Image CDN.

If you have a small website, a semi-manual process is fine. Just find a GUI app or command-line tool, whatever you are comfortable with, you want one that will give you bulk resizing, transcoding, and optimization. You just want to point to a folder and have it spit out all of the alternative versions for you. You can add the markup to your page yourself, albeit this can become a bit tedious. You can automate the insertion of this markup with some tools also.

If you use a static-site generator or a backend platform like Wordpress, there are integrated options to do this for you such as:

For some of these plugins, some JavaScript may be required. It only makes sense to venture into Nuxt if you already know Vue, and Gatsby if you already know React. You could muddle through with Eleventy Image without knowing that much JavaScript. Ben Holmes discusses how you can use Eleventy Image with for any web framework if you want to give that a go, but there are more generic options in that area such as squoosh cli.

I mention these just to demonstrate that this is something that frameworks are treating as a key concern now, which is good. People solving the same problem for their particular platform seems a bit wasteful in the grand scheme of things, but that’s where we’re at at the moment.

What about background images?

Background images are specified in CSS with background-image.

For now, it’s probably best to stick with media queries if you want to swap out background images for different screen resolutions.

There is image-set() which can take multiple resolutions of an image and let the browser make the best decision about which one to use. It is not quite ready for prime-time yet, there are caveats. If you want to explore this further, you can read this article - Using Performant Next-Gen Images in CSS with image-set.

I would think you’re in an excellent state if you have done this much. Of course, there are other optimizations you can do. You have to draw a line somewhere. Test your website out and see if more is necessary.


This was meant to be a quick article! Anyway, I hope I shed some light on a few bits and bobs with regard to images on the web. Images shouldn’t be hard. We shouldn’t feel like we are spinning plates when we are building websites! Happy coding!

Image Attribution

The featured distribution graphs are courtesy of the HTTPArchive with rights reserved under a Apache License 2.0.