Zach’s ugly mug (his face)

Zach Leatherman

The Crushing Weight of the Facepile

June 10, 2019

I was cruising my own web site with DevTools open (as one does) and browsed to my latest blog post only to be slapped in the face with a 3.7MB web site. My web site (on a blog post page without content images) is normally about 400KB. What happened??

Astute readers of blog post titles may already be ahead of me here—the more successful the blog post got on social media, the more webmentions it got, and the cluster of avatars below the post (the facepile, as it is known) grew and grew.

What should I do?

  1. My first instinct was to make the images intrinsically smaller. Solve the problem at the root! Some of them came back from’s avatar cache as 256×256—one was 135KB 😱. I filed an issue upstream for this. But I needed to solve this problem immediately, locally.
  2. Use Cloudinary or imgix or Netlify Image transformations or some other free-for-now or free-metered or fully paid service to resize these automatically. I started down this road and decided it was a little much for a personal site.
  3. “Zach, you’re just being vain—simply cap the list and only show a few of these images maximum.” I mean, yeah, I guess. But I also like investing in showcasing webmentions fairly prominently on my site because I’m trying to be an advocate for IndieWeb.
  4. Use loading="lazy" to lazy load these images. I was already doing this but browser support is non-existent, currently.
  5. Take control of it myself and use IntersectionObserver to lazy load them only when they come into view. IntersectionObserver browser support is pretty good now. I decided to go with this low hanging fruit for now (at least as a short term solution).

Enter IntersectionObserver


<img src="/web/img/webmention-avatar-default.svg" data-src="…" alt="Author name" class="webmentions__face">


if( typeof IntersectionObserver !== "undefined" && "forEach" in NodeList.prototype ) {
var observer = new IntersectionObserver(function(changes) {
if ("connection" in navigator && navigator.connection.saveData === true) {

changes.forEach(function(change) {
if(change.isIntersecting) {"src","data-src"));

document.querySelectorAll("img[data-src]").forEach(function(img) {

This means that if JavaScript hasn’t loaded yet or failed somewhere, these avatars would stick with the default image I’ve specified—I’m okay with that.

I also added a little check to skip the load if the Save-Data user preference was enabled.

Bonus: Details

The other great thing about using IntersectionObserver is that if the images aren’t visible they aren’t loaded. For example, I hide Replies and Mentions inside of closed <details> elements.

  1. Jeremy Swinnen

    Jeremy Swinnen @jereswinnen

    TOTAlLy GOnNa STeAL THiS foR MY BLOg 😉#

If <details> is supported by the browser, the avatar images will only load on demand when the user expands <details> to show the content! I love it. And if <details> is not supported the avatars are lazy loaded just like any other content.

Zach’s ugly mug (his face)

Zach is a builder for the web with Netlify. He created the Eleventy static site generator and is still fixated on web fonts. 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 »

Spicy fonts and static sites 🌶️—JS Party Episode #79
Week Notes №2 ending 7 June 2019


Rhy MooreRussell Heimlich
вкαя∂εℓℓKyle HallDan DenneyAndy DaviesJohn MeyerhoferAaron PetersBen SevenPelle WessmanRussell HuqHaDoukenMike Aparicioam I jacky? or am I jacky's dog? lol 📟🇭🇹✊🏾Nicolas KaenAmelia Bellamy-RoydsEric PortisTatiana MacHolger BartelRhy MooreKeith ClarkNickSøren BirkemeyerroyciferBrett EvansDave RupertTatiana MacMichael Scharnagl
1 Bookmark
  1. Jeremy Keith

    Jeremy Keith #

    The Crushing Weight of the Facepile— June 16th, 2019 Using IntersectionObserver to lazy load images—very handy for webmention avatars.

25 Replies
  1. Max Böck

    Max Böck @mxbck #

    yeah not sure what the DSGVO says about this. Technically I'm getting them from, which does the caching of twitter avatars on their end... I do have some text in my privacy policy about it though. No idea if it holds up in court. 😅

  2. Atila 🧉

    Atila 🧉 @AtilaFassina #

    yeah, I think that's the best approach. I removed mine for now because UI just got waaay too cluttered (and useless, tbh). But it's in my backlog to do something like that too!

  3. Max Böck

    Max Böck @mxbck #

    did something similar on my site - show just 5 lazyloaded avatars and reveal the rest only when requested 👍

  4. fluffy 💜 ✪▾̫✪

    fluffy 💜 ✪▾̫✪ @fluffy #

    Oh dang I should add this to webmention.js (especially the save data pref)

  5. Amber Wilson

    Amber Wilson @ambrwlsn90 #

    True, although I think I'm more up for the default hiding of the images until the person decides they wanna see them 🙂

  6. Darek Kay

    Darek Kay @darek_kay #

    Almost two years after Zach's post, loading="lazy" has now good browser support (73%). Best trade-off between effort (adding a single attribute) and gain. Bonus: no JS required.

  7. Paul Kinlan

    Paul Kinlan @Paul_Kinlan #


  8. Zach Leatherman

    Zach Leatherman @zachleat #


  9. Thomas Steiner

    Thomas Steiner @tomayac #

    I read this and immediately checked my Web mentions code. Past me actually was smart enough to stick `loading="lazy"` on the avatars (self high five); I had just missed the fallbacks. Fixed in…. Thanks for sharing these stories (both Zach and Brian).

  10. вкαя∂εℓℓ

    вкαя∂εℓℓ @briankardell #

    facepile facepalm 🤦‍♀️

  11. André Jaenisch

    André Jaenisch @AndreJaenisch #

    so LAzY LOaDing ALl iNSteAD of pAGiNATIon?

  12. Jeremie Carlson

    Jeremie Carlson @JeremieCarlson #

    Ah probably lack of context on my part. I thought you were hiding the webmentions by default and I had a thought that those links would be good for SEO and wondered what the impact might be

  13. Zach Leatherman

    Zach Leatherman @zachleat #

    of not loading the images?

  14. Jeremie Carlson

    Jeremie Carlson @JeremieCarlson #

    Possible SEO impact?

  15. Luke Bonaccorsi 🏳️‍🌈

    Luke Bonaccorsi 🏳️‍🌈 @CodeFoodPixels #

    🕵️ Eleventy Open Collective Zach Leatherman Zach Leatherman

  16. Zach Leatherman

    Zach Leatherman @zachleat #

    hey no peeking! 😎 Luke Bonaccorsi 🏳️‍🌈 Eleventy Open Collective

  17. Luke Bonaccorsi 🏳️‍🌈

    Luke Bonaccorsi 🏳️‍🌈 @CodeFoodPixels #

    I just snooped the repos, the avatar-local-cache stuff is pretty neat! I'm already resizing stuff on the LeedsJS website, but doing the deciding between formats is pretty nice! Eleventy Open Collective Zach Leatherman Zach Leatherman

  18. Zach Leatherman

    Zach Leatherman @zachleat #

    I thought about that, yeah. But I wanted it to work if the user opted in to Save-Data after page load but before scrolling down. An incredibly small detail 🤷‍♂️

  19. Nicolas Hoizey

    Nicolas Hoizey @nhoizey #

    Don’t you think we could move the saveData test outside the observer, to test only once per page load?

  20. Nicolas Hoizey

    Nicolas Hoizey @nhoizey #

    I had the same issue with 119 webmentions on…, and tried multiple solutions already, but I think yours might be the best yet, I'll have to try it, thanks!

  21. Zach Leatherman

    Zach Leatherman @zachleat #

    I think they are ace, yes—but I have had a social hole on my website ever since I deprioritized and eventually removed disqus

  22. Philipp

    Philipp @kleinfreund #

    Should I webmentions?

  23. Zach Leatherman

    Zach Leatherman @zachleat #

    Yeah—I wrote a little about it here:

  24. nystudio107

    nystudio107 @nystudio107 #

    How are you doing the Twitter likes / etc. with the facepile to begin with? It appears that you're indirectly using

  25. Zach Leatherman

    Zach Leatherman @zachleat #

    Ha! My mom tells me that all my posts are cool

    8 Mentions
    1. Amber Wilson

      Amber Wilson @ambrwlsn90 #

      Am soon gonna update my webmentions along the lines of how @zachleat and @adactio handle theirs. See a post here Because why should everyone scrolling to the end of a post of mine have to download 20+ images at about 20kb each? 😉 #IndieWeb

    2. Nicolas Hoizey

      Nicolas Hoizey @nhoizey #

      @zachleat reading… , I wonder why you "cut the mustard" with both `typeof IntersectionObserver` and `"forEach" in NodeList.prototype` as it seems all browsers supporting the former also support the later.… developer.mozilla… Truncated

    3. Веб-стандарты

      Веб-стандарты @webstandards_ru #

      Крах под тяжестью лиц. Зак Лезерман оптимизирует загрузку аватарок комментаторов с внешнего сервиса в своём блоге —

    4. admin #

    5. #

    6. Adactio Links

      Adactio Links @adactioLinks #

      thE crusHing weight Of THe FaCEPILE—zAchlEAt.COm zacHleAt.coM/weB/fAcEpiLE/

    7. #

      Running a routing performance check on my blog I noticed that in the list of domains being accessed included Except, I don't have anything to do with Facebook on my blog and I certainly don't want to be adding to their tracking. I was rather pissed that my blog cont… Truncated

    8. Noice. In particular: "I also added a little check to skip the load if the Save-Data user preference was enabled." - @zachleat I've been wary of this potential problem, but my posts aren't cool enough…

    Social Card Image Preview

    This is what will show up when you share this post on Social Media:

    How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)