I was tired of building 474 separate pages for Open Graph Images
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:
- zachleat.com (this is the web site you’re on now)
- build.awesome.me
- speedlify.dev
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 😘).

19 Comments
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.
Khalid ⚡
@zachleat Jokes on you, I can make whatever OG image I want with any text I want :P
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)
Zach Leatherman
@khalidabuhakmeh if it ain’t hosted on my domain it’s a deepfake (and you could do that before!)
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.
David Bushell ????
i'm confused, are you generating the same 474 images every build (or worse, every request!)?
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-...
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
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
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 ;)
Zach Leatherman
@robb *yes*
David Bushell ????
ah gotcha, nice
Zach Leatherman
@khalidabuhakmeh *backs away slowly*
Christopher Kirk-Nielsen
@zachleat Ooh, clever! I should see if this helps get my build times down a little… thank you for sharing!
Zach Leatherman
@chriskirknielsen you’re very welcome!
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
Christian Alder
Thanks for the write up Zach!
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
Zach Leatherman
you’re very welcome (and thank you for the kind words!)