Auto-Generating Open Graph Images on a Static Astro Site
There are some nuances to getting Satori working right.
Intro
As I start writing a bit more on this site, I don’t want to have to manage Open Graph images. Surely there must be a smart way to generate them automatically, right? It’s 2024!
This site runs on Astro, which is seriously awesome. And I found two plugins that looked like they might do the trick: astro-og-canvas and astro-opengraph-images. But I fiddled with both and couldn’t get either to work with my setup.
Then I found this writeup on how to do it from Arne Bahlo. It was a nice short post with just a little bit of code. Neat! Let’s go.
Building The Image Layout
Since Arne’s technique uses Satori, he recommends getting used to its layout engine by using the OG Image Playground. Here’s what I wanted to generate automatically for each post on this site:
But, for reasons we’ll see in a sec, don’t expect to just be able to copy the code you come up with in that playground (at least if you’re running a simple static Astro site like me).
Installing Dependencies
First, npm install satori sharp
to get the dependencies we need.
Then make an endpoint like Arne describes:
Make sure you actually have Roboto (or whatever font) present in your public directory, or update that URL as needed. Any WOFF or TTF/OTF file should work.
Now you visit YOUR_SITE_URL/og-image.png you should see a nice simple “Hello World” image. Cool! We’ll move this code in a sec, but we just wanted to get it working.
No React? No Problem …But There’s a Catch
This is where I thought I could just copy/paste the stuff I had from the OG Image playground, but alas, it didn’t work. Because according to Astro…
An appropriate UI framework (React, Preact, or Solid) is required to render JSX/TSX files. Use
.jsx
/.tsx
extensions where appropriate, as Astro does not support JSX in.js
/.ts
files.
Welp, I don’t have React on this site, and I wasn’t really looking to add it just for image generation.
BUT, if we build our image using the syntax Arne uses above, as mentioned on the Satori README, it’ll work!
Alright fast forward a bit…
Here’s what I went with for this site. I actually used chatGPT to translate the JSX version I built on the playground into this React-elements-like objects syntax. Here’s the full utility function:
There’s some stuff in there I had to figure out - getting the images to work in general, and getting dynamic titles/images.
Working With Dynamic Routes
This site uses dynamic routing to display the posts. Meaning, for my Work
posts on the other side of this site, the simplified directory structure looks like this:
So to get that Open Graph image dynamically generated along with my posts, I had to add a new API route there:
That means on my site we’ll be able to go to /work/{slug}/og-image.png
to get any given Open Graph image.
And that file looks like this:
A given work entry has some props defined in the front matter:
That’s what lets us pull in the Title and Thumbnail Icon in the generateOgImage()
function.
BUT one tricky bit with the images - if you store them in the src/
directory like I do to take advantage of the Astro <Image />
component, the simplest way to use them is to convert them to base64 data.
Here’s the magic bit in my generateOgImage()
function that gets that relative URL of the image in the markdown front matter to the Satori generator in a way it likes:
- Grab the post/entry
- Grab the thumb if it exists, get that file system path of that image
- Convert it to base64
- Construct a full base64 url
Then we can use thumbBase64String
in the function as seen above in this bit…
I also have the profileImageData
just living as a base64 string in the src/
directory, since that’s constant on each Open Graph image.
Whew! Now we’ve got a nice Open Graph image for each page, without manually making them. Throw a /og-image.png
on the end of any post on this site to see it in action.
I know this setup might be a bit idiosyncratic to this site, but hopefully it helps some poor soul trying to do something similar.