Approaches for dynamically creating opengraph images

I’m curious how others are handling dynamic generation of opengraph images for pages. Are you handling it in Hugo at build time, or when the pages are requested?


I set up a dynamic process where I create a JWT for each page consisting of the page title, permalink, and fully qualified image to use in the OG image. This JWT hash is added to the <meta> tag for the image that points to a serverless process (Azure Function, but that’s not important).

When the image was requested, my serverless process would look to see if the image had already been created & saved to my CDN (separate from the Hugo site). If so, it responded with a 301 to the CDN URL. If not, it generated the image, saved it to the CDN, and then responded with the 301.

The reason I created a remote process was because I have two sites, one with thousands of content pages that I didn’t want to deal with pre-generating the images & adding to my git repo.

The process has flaws, but it’s worked fine enough. I’ve wanted to optimize it, but put it off. Now, the component I use to save the image is broken and requires a decent amount of rework.

So before I go about fixing bug & addressing some flaws, I wanted to check and see how others handle this.

@eliostruyf has a method he describes here.

@bwintx has a method described here

1 Like

The method I mentioned — thanks for the plug, @gaetawoo :slight_smile: — is not fully dynamic in that you still have to create the title.png file manually for each page. If you want something more automatic and you’re not averse to a Node/JS-based approach, you might try Jason Lengstorf’s get-share-image package.

Thanks for the replies. I’m familiar with @eliostruyf’s solution… a manual process is not ideal for me as one site has thousands of content pages, the other has hundreds. While @bwintx’s solution works, it also isn’t fully dynamic & automatic, which I need.

Based on these options, I’ll stick with what I’ve done and just address the flaws I have with my existing setup. I don’t want to setup another 3rd party dependency with Cloudinary.

The problem with the current one is the underlying serverless runner I’m using (Linux on Azure Functions) is missing a dependency for Chromium. This means both Puppeteer & Playwright, which I use to take the screenshot of the page that generates the dynamic image, fail.

I’ve already been migrating things from using Azure Functions/AWS Lambda > containers to have less of an underlying dependency on the hosting arch. Using containers will yield more control.

Take a look at the theme for the Hugo docs site, specifically:

It starts with an empty image, then overlays the desired text.

Thanks… I’ve been looking at that template & the image processing stuff in Hugo. It’s one approach I’m considering… I like how the image is generated at build time. One thing I’m trying to figure out with this approach is how to do more than just overlay text, but also overlay an image.

For example, here’s what one of my OG images looks like:

The image in the top-right comes from this image:

I haven’t worked with the image processing feature much, so just not sure how much I can do with overlays, or, if I can do this, how much of an impact it will have on the build time with a site that has 1000’s of content posts.


You, sir, just made my Christmas card list. Thanks! Send me your address and I’ll include a fruitcake :slight_smile:

1 Like

please update when you have an output or solution, i’m very curious to see

Will do… likely going to take a couple days as I’ve got a full plate, but I’ll follow up.

One thing I want to do is have a few different versions of OG images created, so it’s going to be conditional:

  1. when no image is specified, default to what I currently do (no image in the top-right like you see in the thread above)
  2. when there is an image specified, but it’s not a “full-screen thumbnail”, like the image in the thread above, do what I do above
  3. when there’s a full-screen image specified, like what you’d use for a YouTube video thumbnail, only use that and add nothing else to it