Zach’s ugly mug (his face) Zach Leatherman

If I work really hard on my Open Graph Images, People will share my Blog Posts

August 01, 2021

Open Graph images. Those little images that show up when your site is shared on social media. The ones for my site were… bad. My default was to use one, huge, boring branded image for everything—which provided no extra value and just cluttered up y’alls feeds (sorry!). I wanted to make them better and here’s what I did.

The Lazy Method

While building a screenshot cloud service for, it became obvious that I should add an Open Graph size to use screenshots as Open Graph images. You can see this in use on the Eleventy docs pages:

  • Navigate to
  • With the screenshot service in place, it’s just one line for a custom Open Graph image: <meta property="og:image" content="">
  • The social card image looks like this: Open Graph images for

For the amount of work this took to setup, it was totally worth it (one <meta>!). But I wanted to take it to the next level for my personal blog (with a long term goal of maybe convincing my colleagues to use something like this on 😅).

The Underwhelming Method

In the past, I did experiment a bit with using the SVG to raster image feature of Eleventy Image (via sharp) but it wasn’t quite what I wanted. It had the benefit of being much faster than HTML-based methods (usually based on Puppeteer) but I ended up having to slog through a bunch of hand-written SVG to get it to look how I wanted to look. I’ll admit, I’m not as productive at hand-authoring SVG as I’d like.

The Fancy Method

With those experiences under my belt, I decided to custom design a page on my site (source code) that would iterate over each blog post and could serve as the target for my screenshot service. This provided a couple of benefits:

  • I can use all of the existing HTML/CSS/JS/Fonts already in use on my site. That means fake browser chrome, custom Twitter avatars, Lighthouse scores, popularity ranks, among others!
  • I don’t have to run Puppeteer locally. I try to avoid this when possible—it is awesome but it slows down the build (a lot).

Each individual blog post has a dedicated Open Graph image page, for example /opengraph/web/automatic-opengraph/ (Fair warning: this page is not using responsive web design principles. It is specifically created for the 1200×630 aspect ratio).

If I want a custom background image for a single post’s open graph image, I add front matter with the image url:

  openGraphBackgroundImage: /og/sources/graphpaper.jpg

Next, in my base Eleventy layout file (source code) I point the Open Graph page for each specific blog post towards the screenshot service to capture the image:

It looks like this:

I wanted the image to have the post title, my avatar, the published date, and the URL of the page.

For bonus metadata, here’s one with a Lighthouse score attached (updated regularly via Speedlify), a webmentions count, and even the page weight (e.g. 226 KiB, also driven by Speedlify):

Bonus Features

DevTools Device Emulation

Inside of Chrome Devtools, I added a custom Emulated Device specifically for Open Graph image formats to test the layouts.

Drop down select of Chrome Devtools Emulated Devices

Social Card Preview

I added a little <details> on the bottom of each blog post so that I could test the social card image preview. I’ll probably pivot this to a link-based sharing widget at some point or combine into my existing Retweet to Share component.

On-demand Builder Cache Busting

The Eleventy Screenshot service is an independent project that uses On-demand Builders to cache screenshot image content to Netlify’s CDN. That cache is pretty sticky. The easiest way to invalidate the cache is to trigger a Screenshot service build on Netlify, but that would reset the cache for all screenshots saved by the service, which is too heavy. It has been a common request for a more granular Netlify API to reset pieces of the cache but during the development of this project I realized that we can handle this manually on the app side using our old friend, the Cache Buster URL.

If the old URL looked like this, I can add an arbitrary cache busting key to the end of it (prefixed by an underscore to identify it anywhere in the URL).

If I generated that key with today’s date (e.g., it would generate new images every day without the screenshot service needing to invalidate the entire cache. (Some related discussion about this on Twitter)

< Newer
Fire SVG animations (SMIL) when the SVG is visible
Older >
Building an Automated Screenshot Service on Netlify in ~140 Lines of Code

Zach Leatherman IndieWeb Avatar for a builder for the web and the creator/maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 81 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »


Fynn BeckerEric PortisMWDelaneyDavid EastLuis ConfrariaRyan FillerJon Kuperman🔴 André Jaenisch 🔴Matthias OttEduardo BouçasMichael SpreuPinboard PopularFriday Front-EndActually,IndieWeb Avatar for https://www.pixellyft.comFeedpushr feeds.VisionSnapChris HeilmannGLOBALYNC - HumanityDan 👩‍💻Kim LarsenHolden MadagameEco Web HostingIndieWeb Avatar for https://lastarcher.netHsiangHuiIndieWeb Avatar for https://klmlinks.wordpress.comBob MonsourIndieWeb Avatar for Avatar for https://simpleenews.qblnet.usIndieWeb Avatar for https://www.reddit.comIndieWeb Avatar for https://blog.sweetsaw.xyzIndieWeb Avatar for http://aayugcreation.comTavarroIndieWeb Avatar for https://www.67nj.orgIndieWeb Avatar for https://tutorialsart.comIndieWeb Avatar for https://hnikoloski.comIndieWeb Avatar for https://css-tricks.comIndieWeb Avatar for https://droppost.inIndieWeb Avatar for https://infiniti-cs-news.comIndieWeb Avatar for https://hnikoloski.comIndieWeb Avatar for https://wpnewshub.comIndieWeb Avatar for https://codinghindi.inIndieWeb Avatar for Avatar for https://www.67nj.orgIndieWeb Avatar for


Mark DyckfLaMEdthomas 🌻Denver Prophit Jr.Philippe BernardIsaacJohn TurtonChristian | 👨🏼‍💻 | 👨🏼‍🎨morbidickEric WallaceJordan ScalesBrian DeVries 🔨Paul Shryock (he/him)Yevhen OrlovCharlie GDana Ross 🇺🇸 🏳️‍🌈Davo GalavottiErik HoffmanrazhHaroen ViaenebertrandkellerRhian van EschAdri Van Houdt 💉💉 🏳️‍🌈Ketan ChandekarAmrita Chanda 🏳️‍🌈👩🏾‍💻🍸Kyle Mitofsky #BLMThomas SteinerLennynicTanner DolbyJohn MeyerhoferSanti CrosR. Ilham SastronegoroMatt Biilmannmason conkrightNaNty NaN ProblemsJake DohmRobin CussolEileen WebbBrian MuenzenmeyerMark NoonanMark BuskbjergAdam Di MarioTabsAnders GrendstadbakkRicardo Blanch PMJon KupermanPaul SchaeferMatthias OttAleksandr HovhannisyanAman Mittal ⚛️☕🖖🥑David EastonalfhirJody DonettiBen SeymourPrototypr👁️✨AadeshᴅᴇʀᴇᴋPaul IrishColin FahrionMark Hougaard JensenCristóferson BuenoAlexis DeveriaMax StoiberMatt LehrerjaymeeRyan Mulligan🔴 André Jaenisch 🔴SterenCarles MuiñosStephen MastersRhubarbJuhisJason SackeyEthan MarcotteMarcoSia KaramalegosDave MethvinJabran RafiqueKatie Sylor-MillerDavid EastBen HolmesTom VanAntwerpdies das 🦂 contentyour favorite JackieMike💀HarleyNate MooreDoug WilsonBoris 😷💉💉Fynn BeckerAna RodriguesJohannes 👨‍💻Eric PortisManuhenry ✷Dave RupertDavid K. 🎹
  1. Florens Verschelde


    You had me at hard on

  2. Tim Kadlec


    SUPER snazzy setup!

  3. Aleksandr Hovhannisyan


    I really need to step up my game on this and start automating my OG thumbnails! I do all mine by hand. Also, thank you—your hamburger menu made me lol 😄

  4. Dave Rupert


    I like the background trick!

  5. Zach Leatherman


    I… should that be hyphenated

  6. Florens Verschelde


    may be

  7. Zach Leatherman


    why thank you, Tim!

  8. Zach Leatherman


    Awesome 🙌🏻

  9. Zach Leatherman


    glory be to CSS custom properties

  10. Dave Rupert


    This (almost) inspires me to finish my 3yr Eleventy migration.

  11. Zach Leatherman


    Dave your migration is almost as old as Eleventy 😅

  12. Erik Kroes 🏔


    First and latest user at the same time 👍🏿

  13. castastrophe



  14. Dave Rupert


    To my credit, it’s not on 0.7.1 anymore. It’s at least on 0.10.

  15. Peter Wilson 🏳️‍🌈


    Slight bug in your RSS feeds, the OG image shows up as its own article. It does look lovely at full screen though.

  16. Zach Leatherman


    Fixed, thank you!

  17. Christian | 👨🏼‍💻 | 👨🏼‍🎨


    I love everything about this, thanks for sharing! I will try and adopt a similar method to my blog when I get around to it 😊

  18. Philippe Bernard


    Thanks! Yep, I saw your other article about OG (very interesting, and that's how I found your ImageMagick script)... I expect to build something that might help you convince your colleagues at Netlify 😁

  19. incidentally, the nice big image showing up for the post above on my site in Twitter is shamelessly inspired by @zachleat doing it and explaining how in…, although I implemented it differently. So cheers for that, Zach; useful stuff :)

  20. Christian Miles


    perhaps this is the sort of boring stuff you're alluding to but.. check out how @zachleat does it with @eleven_ty and @Netlify I use it to reasonable effect on, and elsewhere…

  21. Eleventy 🎃🎈 v2.0.0-canary.16


    Awesome! I wonder if you saw this one too:… (and a follow up here…)

  22. Zach Leatherman


    it’s all automated!…

  23. Robb Knight

    Robb Knight

    @zachleat I knew I’d seen some other option for this. Will have a gander later ????

Shamelessly plug your related post

These are webmentions via the IndieWeb and

Sharing on social media?

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)