For the past several months, after leaving Amazon I have been hard at work building a software product. I anticipate probably another 1-2 months before I’m ready to share more details on that, but I wanted to take the time to highlight something that I’ve learned more about while working on this project.
I’ve always enjoyed reducing page weight on the front-end. Some front-end folk can get quite obsessive over this, and I’ve walked that line myself. Through the years our industry has embraced many techniques to accomplish a smaller page weight: concatenating and minifying files, tree shaking to reduce bundle sizes, etc…
I am also a big fan of projects like the 1MB Club that celebrate sites that put in extra work to make sure they are performant.
There is a site on the 1MB Club that I built in 2016 that I am still very proud of. I had to make a lot of (at the time) hard decisions to get it to cold load under
1 MB. Things like removing jQuery and writing native JS, reducing the number of custom fonts I was loading, and using gulp.js to crunch all the empty space scripts and stylesheets. I was like an ultralight backpacker giving away all my Clif bars to make weight.
By far the biggest task (both in time spent and impact), was optimizing the photographs. The site uses a lot of stock photography, and I had to make sure they were as small as they could be.
This is totally skimmable/skippable if you know this stuff already.
Photographs are almost always saved and distributed digitally using the JPEG file format. Even when you purchase them from a stock photo site. JPEG is a lossy format, which means that no matter what, some information that was captured by the camera was lost when it saved as a JPEG. There’s a lot of nuance here that I am not going to go into for brevity (yes I know what RAW is, save your emails) but the end result is that some lossy conversion process is performed in the camera and you end up with a
.jpg — they are interchangeable) file.
Another major image file format for the web is PNG. It is lossless, but historically was only usable for things like digital graphics, or if you manually had cut an object out of a photograph and wanted alpha transparency. Re-saving a photograph that has already undergone some form of lossy conversion to PNG format does not improve the quality since that data has already been removed from the photo. In fact, saving a JPEG file as a PNG will often 10x the file size and accomplish nothing.
For a long time, if you wanted to put a photograph on a website, your only practical choice was to use JPEG. To make things worse, you often had to size down a very large JPEG (maybe 4000 x 2000) to a smaller JPEG (like 1200 x 600) and strip metadata, as well as possible manually set the quality percentage lower to hit a file size target. This resulted in the lossy compression process running a second time, causing the photo to lose even more detail. If you needed to edit this photo again at a later date, you better have saved the first version. Because every time it is saved again as a JPEG, it loses quality.
Lossy compression generally works using a percentage value, from 1 - 100. The lower the value, the more the algorithm compresses the image which lowers the file size but also removes detail and adds visual distortions called artifacts. Creating files of photographs to be used on websites was often the result of a careful compromise of file size vs. quality — tweaking that percentage slider to find the best combination.
During the 2010s, screens got better. “HiDPI” and “Retina” became common on monitors and smartphones. This made things even harder, because photos that had previously successfully achieved that balance before now looked awful. I believe that the shift towards using illustrations in web design during this time was partially prompted by this. Aside from being more trendy than a photograph, an illustration could be represented by a PNG or SVG that is usually much smaller (like
<150 KB.) Where even a carefully optimized and compressed JPEG can be
There were ways created to conditionally load images (media queries can detect display density and HTML introduced the picture element) but browser support was bad and let’s be honest, unless there’s an automated system handling it for you it’s a huge pain. What ended up happening is that for images that could be PNGs, they were saved at 2-3x size but added to the page at their desired dimensions so the browser rendered them crisply on HiDPI screens (this eventually just switched to using SVGs in a lot of cases) and for JPEGs, they simply made them larger. This was part of the reason the average page weight of websites grew.
But browsing around the web today in 2023, you will find that if something is a photograph, it’s probably still a JPEG. This is a shame, because there have been major improvements in this area in the form of new image file formats.
I don’t consider myself a “cutting edge” technology person, I often don’t learn about a new technology until it has been around for a bit and is increasing in popularity. I recently fell in love with Svelte even though it’s been around since 2017.
I did not learn about the
.webp file format until a couple of years ago even though Google first released it in 2010. In fairness, it wasn’t practical to use even with a fallback until around 2015 and only became usable without a fallback in the last 2-3 (ish?) years.
For those who don’t know what WebP is, skim the Wikipedia article for more detail but the important bits are:
- It’s an image format that supports both lossless and lossy generation. This means WebP can be used in situations you would previously have used both PNGs and JPEGs. though for photographs you should probably still only use it in lossy mode.
- The file size of WebP images is much smaller than JPEGs and PNGs in both lossy and lossless modes. Google quotes a 26% smaller size than PNG for lossless. That’s an impressive figure on its own, but its real strength lies in its lossy compression.
A photo I took during a Día de Muertos parade I went to a few months ago. (I’m not a photographer and this was the first cool photo I found scrolling my camera roll.)
Let’s put it to the test. I pulled this photo off my phone. In its original state it was
3024 x 4032 and had a
1.4 MB file size. Since 2017, iPhones have used a file format called HEIC for photos (unless you have a newer high-end model and the ProRaw feature turned on) which is similar in size to JPEG and goes through a lossy compression process, it just uses a different codec and attempts to save more data from the camera sensors and allow more ability to edit the photo. This extra data is also used by automated software processing in smartphone cameras, but that’s an interesting side tangent.
Because we are dealing with a photo that has already gone through a lossy process, converting it to lossless would be pointless. Let’s convert this to JPEG and WebP, using two different percentages for the lossy algorithms: 95%, a very light touch, and 65%, closer to a real world choice.
Additionally, I scaled this photo down to 1/3rd of its original size:
998 x 1331 because I think they will be easier to compare if they are smaller and I have my own bandwidth caps to worry about if this post receives a lot of traffic.
Converted to JPEG at 95%
Converted to WebP at 95%.
|360 KB ↓ 25.9%|
Converted to JPEG at 65%
Converted to WebP at 65%. Apparently the lower you go, the greater the effect. I really can’t tell the difference between the two images, either.
|90 KB ↓ 65%|
These numbers aren’t just good - they’re incredible.
It’s hard to determine the level of adoption that WebP has had, but my feeling is that it is rather low. I have tried to upload .webp files to many web applications and it was not allowed. A cursory check of big sites like Amazon.com shows they’re using jpegs for pretty much everything still. Even Imgur.com, a platform which I think would stand to gain the most from using this format seems to keep the format the image was uploaded in - JPEG or PNG. Not only do they not convert user uploaded images - they don’t support WebP at all. I tried uploading one.
I’m not sure why there hasn’t been a move to adopt WebP yet. The cost savings on object storage alone would make it worth it, even if you didn’t care about page loading size. You’d be cutting down heavily on egress fees without giving anything up. There might be a lingering impression that it’s not widely supported, or decision makers don’t know about it. But the purpose of this post is to raise awareness.
That’s not all! During my research, I encountered a second image format that is even better than WebP at compressing images! †
† (…in some cases. 😇)
.avif uses tricks that were invented for video compression and applies them to images. It also supports both lossless and lossy modes. This is the newest of the formats I’m outlining here, it debuted in 2019.
AVIF is also the riskier format to use in production as it’s not yet supported by Microsoft Edge and has only partial support in Safari and Firefox. In the next year we can probably expect to see the situation improve.
But is it better than WebP, and if so by how much?
Some notes about this test: This format is new and I had to convert the HEIC image to another format before converting to AVIF, because AVIF does not yet support the codec used by HEIC. Additionally, there were some other differences in how I tested this which I will explain below.
I converted the HEIC file by using Mac OS (Apple has a built in HEIC file converter) to save it as a PNG. Then I used Sketch to export a smaller version of that PNG at the exact dimensions of the other photos. I chose to use PNG instead of WebP lossless to be absolutely sure that the WebP format had not touched the resulting AVIF files at any point. I also left in all the metadata and used none of the normal optimizations for web that are available. The resulting PNG file was
2.5 MB and as close to the source HEIC file as I could think to make it, but it did lose some data because PNG files cannot handle the same type and amount of information as HEIC files can.
Secondly, an interesting quality about AVIF is that its lossy algorithm gives you a second lever to control the quality of the compression: CPU utilization (called Effort.) Other compressed formats only let you select a quality percentage, but AVIF allows you to choose how hard you want the CPU to work. The resulting file size is directly correlated to this, and file size is the only metric we’re looking at here. For the purpose of keeping the comparison as close as I could, I chose the maximum value allowed for “effort” and the 95/65 percentage values for “quality” when converting.
The results I got were consistent at the tool level (same settings + same tool = same file size) but the output from the tools was inconsistent (same settings + different tool = very different file sizes.) Notably, the website produced much smaller files compared to the CLI, in some cases 50% smaller. I carefully reviewed the CLI settings and believe I was using them correctly. All of this was done on a MacBook Pro with an M1 Pro CPU. The website uses your own machine via WASM/Rust to do the conversion.
The only theory I have for the discrepancy is that WASM/Rust is just much more efficient than the Node runtime (or there’s a bug in the Node package) if that is the case, your experience with this image format is going to vary highly depending on how much CPU/what environment you can give to it. If you were converting images to be uploaded to an object storage service to this format, you might consider a compute optimized VPS vs. an undifferentiated serverless option. Running a Node based lambda for this would probably not be a good idea.
Here’s the WebP images compared to the AVIF images I got from the website.
Converted to WebP at 95%.
Converted to AVIF at 95%.
|256 KB ↓ 28.9%|
Converted to WebP at 65%.
Converted to AVIF at 65%.
|94 KB ↑ 4.4%|
A nice 29% decrease in file size at the 95% tier, but at 65% the AVIF file actually was slightly larger than the WebP. I tested this multiple times in two different browsers. I even closed as many other processes as I could to see if that would have an effect. It came out to 94 KB every time.
AVIF seems to not be linear in its compression efficiency at different quality levels and may be better used for some types of images than others, luckily even in this scenario where it came out larger than the WebP file it was only by 4 KB.
I’ve linked them all for you to compare. I was hard pressed to spot a difference comparing JPEG, WebP and AVIF at the 65% level. The only thing I noticed is that the AVIF file seemed more washed out - but that might be because it lost color profile data when I switched from HEIC to PNG.
The future is here and it’s amazing.
Regardless of whether AVIF ends up being better than WebP, it’s clear that there are viable alternatives you can use today that are massive improvements over JPEG. When I think about things our industry has done to improve performance (CDNs, Edge functions come to mind), this ranks as one of the easiest wins I have ever discovered. But it’s seemingly ignored.
Switching to WebP could cut the egress cost of your cloud bill down by an amount that would make it a good line item on a resume or a thing to discuss in a performance review.
My new project uses custom artwork in a big way and I will definitely be using WebP for everything I can. Also, all user uploaded images will be converted and stored as WebP unless I find some compelling reason for this being a very bad idea. We’ll see how it goes.
Woohoo 🥳, first blog post of 2023 and it’s still January! If I had made some kind of resolution to blog more I’d be crushing it right now.