runtimeterror/content/posts/dynamic-opengraph-images-with-hugo/index.md

4 KiB

title date draft description featured toc comments thumbnail categories tags
Dynamic Opengraph Images With Hugo 2024-02-18 true This is a new post about... false true true thumbnail.png Backstage
hugo
meta
selfhosting

I've lately seen some folks on social.lol posting about their various strategies for automatically generating Open Graph images for their Eleventy sites. So this weekend I started exploring ways to do that for my Hugo site.

During my search, I came across a few different approaches using external services or additional scripts to run at build time. I eventually came across a post from Aaro titled Generating OpenGraph images with Hugo which seemed like exactly what I was after, as it uses Hugo's built-in image functions to dynamically create the image.

I wound up borrowing heavily from Aaro's approach and then adding a few additional capabilities, notably the ability to overlay a designated thumbnail image on top of the resulting OpenGraph image. I'm sure this could be further optimized by someone who knows what they're doing1.

In any case, here's what I did to get this working.

New resources

Based on Aaro's suggestions, I started by creating a 1200x600 image to use as the base. I used GIMP for this. I'm not a graphic designer2 so I kept it simple while emulating the "logo" displayed at the top of the page:

Red background with a command prompt displaying "[runtimeterror.dev] $" in white and red font.

That fits with the theme of the site, and leaves plenty of room for text to be added to the image.

The site uses the Fira Mono font family, and I'd like to use that to ensure the share image is consistent with the site styling. So I also grabbed FiraMono-Regular.ttf from the Fira GitHub.

I stashed both of those resources in my assets/ folder: File explorer window showing a directory structure with folders such as '.github/workflows', 'archetypes', 'assets' with subfolders 'css', 'js', and files 'FiraMono-Regular.ttf', 'og_base.png' under 'RUNTIMETERROR'.

OpenGraph partial

Hugo uses an internal template for rendering OpenGraph properties by default. I'll need to import that as a partial so that I can override its behavior. So I drop the following in layouts/partials/opengraph.html as a starting point:

// torchlight! {"lineNumbers": true}
<meta property="og:title" content="{{ .Title }}" />
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:locale" content="{{ .Lang }}" />

{{- if .IsPage }}
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
<meta property="article:section" content="{{ .Section }}" />
{{ with .PublishDate }}<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
{{ with .Lastmod }}<meta property="article:modified_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
{{- end -}}

{{- with .Params.audio }}<meta property="og:audio" content="{{ . }}" />{{ end }}
{{- with .Params.locale }}<meta property="og:locale" content="{{ . }}" />{{ end }}
{{- with .Site.Params.title }}<meta property="og:site_name" content="{{ . }}" />{{ end }}
{{- with .Params.videos }}{{- range . }}
<meta property="og:video" content="{{ . | absURL }}" />
{{ end }}{{ end }}

  1. Like Future John, perhaps? Past John loves leaving stuff for that guy to figure out. ↩︎

  2. Or web designer, if I'm being honest. ↩︎