To be honest, the switch from a classic CMS to Hugo had a steep learning curve at first, but Hugo is incredible. Even though we have static pages, you can do a lot of “dynamic” stuff.
The Problem
From the start when I began using Hugo, I had an issue with the open graph images, per default you can specify one image as default in hugo and use post header mages as og:image. Well - I try to keep my blog posts free of images which are not beneficiary to the content. Screenshots are fine but general pseudo matching placeholder images just that you have something for the header 🤷♂️. So the problem that came up was all posts ended up with the same image when you linked to the article.
The Solution
I found an Article from aarol who had similar issue some years back. Thanks for writing about it! Thats the reason I’m doing this post, it might help someone else avoid the same.
The Idea is simple: Instead of having a dull placeholder we can use hugo image manipulation functions to automatically create an fitting og image for each post. You specify an base background and it automatically take the post title and write an og.jpg to the filesystem and insert the meta accordingly.
How to ?
I Link to the Original Article from aarol here as base. I changed and updated it for my implementation a bit.
Partial override for the opengraph template. Always take the latest version from here before you start and put it into:
layouts/partials/opengraph.htmlI removed the original static / header based og:image part from it completely because I dont want that. Afterwards I added this modified code:
{{/* Generate opengraph image */}}
{{- if .IsPage -}}
{{ $img := resources.Get "og_base.jpg" }}
{{ $Font := resources.Get "/Lexend.ttf"}}
{{ $img = $img.Filter (images.Text .Page.Title (dict
"color" "#ffffff"
"size" 58
"linespacing" 2
"x" 42
"y" 272
"font" $Font
))}}
{{ $img = resources.Copy (path.Join .Page.RelPermalink "og.jpg") $img }}
<meta property="og:image" content="{{$img.Permalink}}">
<meta property="og:image:width" content="{{$img.Width}}" />
<meta property="og:image:height" content="{{$img.Height}}" />
<!-- Twitter metadata (used by other websites as well) -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ .Title }}" />
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}"/>
<meta name="twitter:image" content="{{$img.Permalink}}" />
{{ end }}
- Different to Aarols implementation I simplified it and only use one generic font and block for the Title only. I just want an eye Catcher where display of the page.Title is enough. But beside that same same.
- Re-build you page and you should see all the og.jpg images created.
- As a tip, make sure your base image isn’t to large or all generations will be even larger. If you just use a plain background use png, otherwise if you use a picture like in my example use a compressed jpg.
The Result
The Actual generated result for this Article will look like this:

And Everytime a page link to one of my articles is shared somewhere the opengraph protocol is used, it will look like this Example from a Paste to Discord:

Comments
Nothing is loaded from external services until you click.
With a Fediverse or Mastodon account you can reply to this post. Replies from any compatible server are shown below once loaded.
No Mastodon account? You can leave an anonymous comment in the form below; it will appear after moderation.
Mastodon replies
Web mentions
Leave a comment
No account needed. Comments appear after moderation.
You can also reply to this post on Mastodon and your reply will show up here.