Skip to content

On-the-fly Open Graph images

~3 min read

A high-level look at creating Open Graph images on-the-fly using Cloudflare Browser Rendering and R2.

Wait, this again? #

Not long ago, I posted a guide on how I was generating open graph images for my blog, but this approach had some limitations.

  • Only ran at build time.
    • This meant that pages that render dynamically would either not have an image, or would need their image generated somewhere else.
    • It also meant that since the website hadn’t been built yet, extra steps were needed to provide the appropriate font and other styles to match the website.
  • It would eventually require some strategy of either committing images or build caching to keep build times down.
  • Iterating on the styles had a slow feedback loop.

These have been bugging me ever since I got it working, and now I think I’ve figured out a better solution that will stand the test of time!

The architecture #

In this overview, I’ll refer to “the blog” which is this site, and “the og worker” which generates the images using Cloudflare Browser Rendering and stores/serves the images from R2.

  1. The blog renders an og:image meta tag with URL pointing to the og worker that generates the image. The title and description are put into a JWT that is signed by the blog so the worker can validate that the request for the image was generated by the blog to prevent abuse.
  2. The og image generating worker validates the JWT. If invalid, returns a 401.
  3. Otherwise, it will use the title and description as the file name to look in R2 first to see if the og image for that title and description have already been generated. If so, just return it.
  4. Otherwise, the og worker will need to generate a screenshot. In order to do so, it will need to make a request to the blog at /og-image and pass the title and description as query params. This route dynamically renders the content given to it by the title & description, but it also loads the appropriate styles and font.
  5. The og worker takes a screenshot, and saves it to R2 before returning the image.

What’s great about this #

Looking back at the list of limitations I had found with my previous approach, every issue has been resolved.

  • Every page (static or not) can have an og image by just supplying a title and description.
  • Nothing special for styles like inlining the font as base64. Can also just use tailwind classes easily.
  • Iterating on the styles has a tight feedback loop since it’s just a dynamic page on the blog.
  • Won’t slow down build times.

What could be better #

It takes 2-3 seconds to generate the image, and I suspect that most of that is just spinning up Puppeteer. I’m okay with this since it only needs to happen once per image, and new pages are added infrequently.

If I had a more frequent need to generate the images and needed to cut down the latency, I would consider running the instance of Puppeteer inside of a Durable Object to keep the instance of Puppeteer running between requests.

The relevant code #

If you’re curious to see the implementation of this, check these files on github:

Share this post

📧 Email