In The Cost of JavaScript, Addy makes a really good point: 200kb of JavaScript is more "expensive" than 200kb of images, because the browser needs to do more work to use code compared to images. From the article:
A JPEG image needs to be decoded, rasterized, and painted on the screen. A JavaScript bundle needs to be downloaded and then parsed, compiled, executed —and there are a number of other steps that an engine needs to complete. Just be aware that these costs are not quite equivalent.
This is still very true, but it's a little less significant at this exact moment in history.
With a pandemic sweeping across the globe, I've found that my internet has gotten pretty choppy. Fortunately, because Site Reliability Engineers are both brilliant and tireless, most of the internet is still up and running, but there's definitely something going on—I have a 100mbps connection, but it feels more like 3G at the moment.
This shifts the calculation a little bit. Our devices can still parse and compile javascript at the same speed they could a couple weeks back, but network speeds have gotten slower. So the raw number of bits over the wire is super important right now!
And sites typically have way more than 200kb worth of images; it's not uncommon for a page to have several megabytes of images. Many developers (myself included!) tend not to think about media size much at all.
Happily, there's some pretty low-hanging fruit! In this tutorial, we'll see how we can leverage "next-gen" image formats like WebP. These images are often 2-3x smaller than the legacy formats we know and love (jpg, png). It can make a huge difference.
Watch the video NEW
Prefer your lessons in video format? Watch for free on egghead:
Link to this headingMeet the cast
There are three formats that we can use:
- JPEG 2000 — an iterative improvement on jpgs. Developed in 1997 primarily for use in film and medical imaging. Allows images to be compressed further, with less artifacts.
- JPEG XR — A cousin of
jpeg2000
, developed by Microsoft in 2009 - Webp — a format developed for the web by Google in 2010, focused on using advanced optimization techniques to reduce file-size. Supports transparency and even animation.
We'll spend most of our time today talking about webp
, but we'll revisit the jpeg cousins when we discuss browser compatibility.
Link to this headingHow big of a difference does it make?
A few months ago, I used this image in a post:

I did some experiments, using both jpg
and png
for the source image. I optimized them using imagemin, to see how good these "retro" formats could get.
The results are pretty dramatic:
I've tested it on a lot of images, and it almost always produces files that are 30-70% smaller than even the optimized images!
Link to this headingBrowser compatibility
.webp
enjoys support in most browsers:

Critically, we're missing Safari and Internet Explorer.
How about JPEG 2000?

Alright, so we've filled in Safari, but there's still that pesky Internet Explorer…

We've hit caniuse bingo! With these 3 image formats, we have perfect coverage across the browser spectrum.
Let's look at how we pick and choose different formats for different browsers
Link to this heading'picture' to the rescue!
HTML has two image media elements: the international pop-star img
, and the niche hipster artist picture
.
picture
is a much newer addition to the language. Its main goal is to let us load different sources depending on resolution or support for a given image format.
Here's what it looks like:
html
The picture
tag supports a bunch of source
children. The browser parses the source
elements in sequence, looking for the first one it can use based on the type
. When it finds one, it works out where the image lives via srcset
, and swaps it into the img
's src
srcset
can do a lot of complicated things, but happily for our usecase, we can treat it the same as src
. Essentially, source
is config, and it plugs the matching value into the img
.
In Chrome, for example, we wind up with something more-or-less equivalent to this:
html
This cascade of sources means that one will match on every browser: Most browsers will use webp
, Safari will use jp2
, and IE will use jxr
.
Link to this headingA lazier alternative
The snippet above excels in its ability to match every possible browser with a modern "next-gen" image format. But it assumes that these images exist in these formats.
If we're creating these images by hand, it's a lot of manual labor. And if we're generating them automatically, it can significantly lengthen our build time; image processing is notoriously slow when done at scale.
On my own blog, which receives very little Internet Explorer traffic, I've opted for a lazier solution:
html
I'm serving the nice and tiny webp
to browsers that support it (Chrome, Firefox, Edge), and falling back to a legacy jpg
for browsers that don't (IE, Safari).
To me, this is an example of progressive enhancement. Everything still works on legacy browsers, but images will be a bit slower to load. This is a trade-off I am alright with.
(Hopefully Apple will get on this train soon though! 🤞🏻)
Link to this headingTesting that it works
The browser devtools will always think that the image has whatever src
you gave it initially. If you inspect it in the elements pane, you'll see that it uses a .jpg
.
To check if it's actually working, the best trick I've found is to right-click and "Save as…". On Chrome, you should get a "Google WebP" file format, whereas on Safari or IE you should get a "JPEG".
You can also check the network tab, to see which was actually downloaded.
Link to this headingConverting images to webp
Google has created a suite of tools to help us work with webp
files. One of those tools is cwebp, which lets us convert other image formats to webp.
If you're on MacOS, you can install the suite with Homebrew:
On other platforms, I believe you'll need to download the appropriate libwebp package from their repository.
once installed, you can use it like this:
-q 80
is a flag to set the "quality", from 1 (worst) to 100 (best). You can experiment with different values. I've found that 70-80 is the sweet spot.cereal.png
is the path to the input file you want to convert.-o cereal.webp
is output path.
Link to this headingAbstraction with React
A component is a brilliant way to abstract over some of the funkiness with the <picture>
element. Here's what I've been using, to glorious effect:
jsx
We can use ImgWithFallback
very similarly to how we'd use an img
tag:
jsx
Link to this headingUsage with styled components
If you use styled-components
or Emotion, you may be used to wrapping images in a styled wrapper:
jsx
Thankfully, this still works with our ImgWithFallback
component. We can wrap it like any other component:
jsx
Link to this headingGatsby Image
If you're developing with Gatsby, the gatsby-image
package already does a bunch of optimizations out of the box, including converting to webp
(though you need to opt in for it).
Gatsby Image isn't meant as a drop-in replacement for img
; it can be a bit more friction to use, but it also comes with a lot of additional magic tricks for your trouble.
Check out the docs for more info.
Link to this headingDownsides
The only real downside I've found so far is that webp
is an annoying format to work with as a user.
Most desktop software doesn't yet support it; I can't open it in Preview on MacOS, for example. This means if I right-click and "Save as…" a webp
image, I won't be able to view it!
Converting a webp
to a jpg
is relatively painless, and a google search turns up many online providers that will do it for free. But still, it's an additional bit of friction. If your site/app encourages users to download images, you might not want to make this switch.
Link to this headingNext steps
I'm pretty happy to have cut the size of images on my blog by ~50%. In addition to the benefits to user experience at a critical time, I'm also expecting that this'll save me some money in terms of bandwidth.
Of course, it doesn't seem practical to manually convert every image I use to webp
. I'm already investigating how I can generate these images automatically from the source jpg
and png
files. Ideally, this isn't something I should ever have to think about, it should happen automatically when I build my site. Expect to see something on that soon =)