Zach’s ugly mug (his face) Zach Leat­herman

I was tired of building 474 separate pages for Open Graph Images

619 September 09, 2026

Prior blog post (if you want the full context): If I work really hard on my Open Graph Images, People will share my Blog Posts (2021 Aug)

Here’s the gist:

Open Graph images are those little pictures that show up when your site is shared on social media. […] Each individual blog post (on my website) has a dedicated Open Graph image page.

These Open Graph pages had the URL shape /web/:title/opengraph/ and there were 474 of them.

This was great from a purist’s perspective (in that the Open Graph pages were built similarly to how I build other websites) but my perspective has changed a bit. I don’t think that server rendering a static page for each of the 474 blog posts is necessary! 🫣

Now I use one /opengraph/ page that client renders (the horror!) the appropriate metadata for the Open Graph image. Client rendering here is an appropriate tradeoff for build time because this page is still going through a Chromium-based screenshot service and the ultimate deliverable is a static image. This page isn’t intended to be consumed by human beings (unless they really want to, I dunno, I’m not your dad).

Note that the caching mechanism hasn’t changed: it still lives in the screenshot service and the images aren’t being regenerated in either method.

I now use this approach on a few places and it’s made implementing Open Graph support on websites much easier to build and manage:

Importantly, there is no freeform mechanism on these pages to modify the content of the images via URL parameter. There is no ?text=… to control the title text, it’s keyed off of the local page URL and that alone. I don’t want y’all to put whatever text you want next to my picture on my website (you have to do that on your website 😘).


< Older
Use Speedlify2 to Continuously Measure Website Performance

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at Font Awesome and the creator/maintainer of Build Awesome (née IndieWeb Avatar for https://www.11ty.devEleventy/11ty), an award-winning open source website generator. At one point he became entirely too fixated on web fonts. He has given 89 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 »

6 Reposts

Christian AlderAxel RauschmayerWeb AwesomeTyler StickaKhalid ⚡soapdog

23 Likes

Patrick Johanneson ???? ????????Christian AlderSevMichaelBurtonJSammySergioPDFopstransclude.devAshur Cabrera ????Timo TijhofMike AparicioWeb AwesomeTyler StickaOlivier ForgetTrevor PiercenazJauntyWunderKindKhalid ⚡triswebFynn Ellie BeckerSimon Cox :SEO:Mastro.{js,ts}
19 Comments
  1. Robb Knight

    @zachleat This is really smart. I just nuked mine recently because I was fed up of making the ~1000 pages it needed. I'll keep this in mind if i want to do them again.

  2. Khalid ⚡

    @zachleat Jokes on you, I can make whatever OG image I want with any text I want :P

  3. Zach Leatherman

    @robb thanks! I forgot to mention in the post that it also solved some sneaky problems with seeing 404 errors if my local build requested a production URL screenshot before it was pushed! If that happens I just show a default now (better)

  4. Zach Leatherman

    @khalidabuhakmeh if it ain’t hosted on my domain it’s a deepfake (and you could do that before!)

  5. Robb Knight

    @zachleat Thats also a good point, I hit that a bunch of times and it's really annoying with Mastodon especially because it caches preview images.

  6. David Bushell ????

    i'm confused, are you generating the same 474 images every build (or worse, every request!)?

  7. Mastro.{js,ts}

    Cool! If the background image is always the same, you can also speed up image generations a loooot by not spinning up a whole browser, but justing using canvaskit-wasm in Node/Deno/Bun, e.g. via github.com/mastrojs/og-...

  8. Zach Leatherman

    - now it’s 1 page for 474 unique images (cached in the screenshot service, not generated every time or every build) - previously it was 474 pages for 474 unique images (also cached in the screenshot service, not generated every time or every build) (I removed 473 pages from my b… Truncated

  9. Zach Leatherman

    yeah! I wanted a 1:1 with a regular website as much as possible in my workflow so I didn’t have to worry about browser support as much in the library

  10. Mastro.{js,ts}

    yeah, as the website changes you'd have to periodically update the background png somehow. Although that could be automated as well ;)

  11. Zach Leatherman

    @robb *yes*

  12. David Bushell ????

    ah gotcha, nice

  13. Zach Leatherman

    @khalidabuhakmeh *backs away slowly*

  14. Christopher Kirk-Nielsen

    @zachleat Ooh, clever! I should see if this helps get my build times down a little… thank you for sharing!

  15. Zach Leatherman

    @chriskirknielsen you’re very welcome!

  16. Amelia Bellamy-Royds

    @zachleat So, to be clear: you are still building 474 open graph *images*, you're just not building 474 separate static HTML pages just to screenshot them to make the images. You're using the Chromium-based screenshot service as the build tool instead of using eleventy … Truncated

  17. Christian Alder

    Thanks for the write up Zach!

  18. Christian Alder

    Ohhh that's clever! Took me a trip to your repo to understand, but it's all clicked for me now ???? Adding the json to the page is the same way I do a "go to random page" feature - great idea! And nice way to keep it secure so folks can't just generate any OG… Truncated

  19. Zach Leatherman

    you’re very welcome (and thank you for the kind words!)

Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

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)