Zach’s ugly mug (his face)

Zach Leatherman

A Featherweight Facepile

August 23, 2019

Ever since Eleventy started an Open Collective to take financial backing (by request, mind you), I’ve had it on my to-do list to add an Eleventy Supporters page to 11ty.io.

Fortunately, this is super straightforward using Open Collective’s lovely JSON API (no authentication or tokens required: 😱 but mostly 🎉). Have a look at the members JSON for the 11ty organization.

As I created the Supporters page from this data I noticed the same recurring problem that had plagued me before when using third party social avatars on my web site: the image dimensions and file sizes varied by a lot.

I wrote The Crushing Weight of the Facepile about this exact issue.

As an example, one individual image was over 500KB. This ballooned the supporters page up to 1.9MB of images—not good.

For the webmention avatars on my own site, I had already implemented a JavaScript solution using IntersectionObserver. Specifically, the images would only be loaded when they were visible in the viewport. This was a nice solution for webmentions because they were supplementary to the main content of the page (the blog post). Using JavaScript seemed like a nice mitigation step. But it didn’t really solve the root issue: the images were too damn big.

For the Eleventy Supporters page, the avatars are the primary content. I didn’t want to use JavaScript as a dependency to load these. I needed to solve the root issue: I needed smaller images.

Enter Avatar Local Cache #

I created a plugin called avatar-local-cache to help solve this problem.

Here’s the things that Avatar Local Cache does:

  1. Saves a remote image URL to the local file system (we no longer need to worry about broken images from a remote service).
  2. Resize it to a maximum width that you specify. It will not upscale smaller images.
  3. Automatically detect the format of the image and create jpeg, png, and webp versions of the image.
  4. Run jpegtran on jpeg files and pngquant on png files to optimize them.
  5. It then sorts these based on file size from smallest to largest.
    • If the webp format is not the smallest, it deletes the webp file from the file system.
    • Then it deletes the larger of the png versus the jpeg.

The return object from the plugin is an array of the smallest possible files you can use to create an HTML <img> or <picture> tag from. It contains one of webp, jpeg or webp, png or jpeg or png.

It might look something like (note that webp is first in the array):

[
{
"name": "splitinfinities",
"path": "img/avatar-local-cache/opencollective/william-riley.webp",
"size": 1028
},
{
"name": "splitinfinities",
"path": "img/avatar-local-cache/opencollective/william-riley.jpg",
"size": 1594
}
]

For 11ty.io, I then iterate over this data to create a <picture> file if the array has two entries (the first is guaranteed to be a webp). It ends up looking like:

<picture>
<source srcset="/img/avatar-local-cache/opencollective/william-riley.webp" type="image/webp">
<img src="/img/avatar-local-cache/opencollective/william-riley.jpg" alt="William Riley" loading="lazy">
</picture>

If the array had only one item it would be guaranteed to be png or jpeg and I could create an <img> tag instead of using <picture>.

Results #

For this particular avatar on the Supporters page, the original URL from the opencollective API JSON was a 1240×1240 jpeg that weighed in at a whopping 121KB.

The maximum width I used for the avatar-local-cache images was 73px. Of course this smaller image would be a smaller file size (and it was): the webp format was only 1028 bytes and the fallback jpg format was 1594 bytes.

For a single image, dropping from 121KB to 1KB is some real nice savings.

Aggregate Savings #

Before implementing avatar-local-cache, the Supporters page images weighed in at a total of 1.9MB.

  • Using png files only, the total weight dropped to 206KB.
  • Using jpg files only, the total weight dropped to 122KB.
  • Using avatar-local-cache’s smart image format selection (webp,{jpg,png} or {jpg,png}), the total weight dropped to 107KB.

For all of these images, dropping from 1.9MB to 107KB is pretty great!

Rollout #

We use social avatars all over the place on 11ty.io. I ended up rolling out this solution to the entire site and it dropped the home page’s image weight from 411KB to 102KB. There are a lot of avatars on the home page (111 😎 at time of writing).

Try it out! #

Try this out (or at least some of the ideas presented here!) and I hope that it helps you create smaller sites. Third party images are tricky to manage, especially if the image providers have different priorities than you do. Take back control of those images and cache them locally (self-hosting is a running theme with me).

You don’t necessarily need to use this for social avatars either. It could work with any image, really. I might extend this to be a more generic thing later.

Let me know if this helps you out!

Zach’s ugly mug (his face)

Zach is a builder for the web with Netlify. He’s currently fixated on web fonts and static site generators. His public speaking résumé includes talks in eight different countries at events like Beyond Tellerrand, Smashing Conference, CSSConf, and The White House. He is an emeritus of Filament Group, NEJS CONF, and still helps out with NebraskaJS. Read more about Zach »

Previous
Two Browsers Walked Into a Scrollbar
Next
NEJS CONF 2019

7 Retweets

RaraPelle Wessman@kant@octodon.socialSami KeijonenMatt BiilmannEleventyFlorian Brinkmann
23 Likes
DanielNick FodenUli TroyoOngki HerlambangChris J. ZähllerRiccardo ErraZach LeathermanJess BuddNicolas HoizeyGᒪOᖇIᗩ ☆♚Matthew PhillipsJo SpelbrinkPelle Wessman@kant@octodon.socialMatt BiilmannEleventyRhian van EschNaomi SeeFlorian BrinkmannEric PortisFrederic MarxCharlie OwenGrant Richmond
2 Replies
  1. Nick Foden

    Nick Foden @NickFoden #

    Fascinating !

  2. Chris J. Zähller

    Chris J. Zähller @czahller #

    I’ve been looking for something like this!

    3 Mentions
    1. annualbeta.com #

    2. Luke Bonaccorsi 🏳️‍🌈

      Luke Bonaccorsi 🏳️‍🌈 @CodeFoodPixels #

      This is a much better version of the optimisation I do for images on the LeedsJS website! I love the checking of which types will give the smallest file size and will definitely be using this!

    3. Max Böck

      Max Böck @mxbck #

      interesting post by @zachleat on how he massively reduced the weight of third-party API images: zachleat.com/web/featherwei…