update draft

This commit is contained in:
John Bowdre 2024-02-18 22:11:58 -06:00
parent 203465fea2
commit c3dc2ce2db

View file

@ -16,35 +16,34 @@ tags:
---
I've lately seen some folks on [social.lol](https://social.lol) posting about their various strategies for automatically generating [Open Graph images](https://ogp.me/) for their [Eleventy](https://11ty.dev) sites. So this weekend I started exploring ways to do that for my [Hugo](https://gohugo.io) 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](https://aarol.dev/posts/hugo-og-image/) which seemed like exactly what I was after, as it uses Hugo's built-in [image functions](https://gohugo.io/functions/images/filter/) to dynamically create the image by layering text on top of a base.
During my search, I came across a few different approaches using external services or additional scripts to run at build time, but I was hoping for a way to do this with Hugo's built-in tooling. I eventually came across a tremendously helpful post from Aaro titled [Generating OpenGraph images with Hugo](https://aarol.dev/posts/hugo-og-image/). This solution was exactly what I was after, as it uses Hugo's [image functions](https://gohugo.io/functions/images/filter/) to dynamically create a share image for each page.
I ended up borrowing heavily from Aaro's approach with a few variants for the OpenGraph image:
I ended up borrowing heavily from Aaro's approach while adding a few small variations for my OpenGraph images.
- When sharing the home page, the image includes the site description.
- When sharing a post, the image includes the post title.
- ... but if the post has a thumbnail[^thumbnail] listed in the front matter, that gets added to the corner of the `og:image`.
- ... but if the post has a thumbnail[^thumbnail] listed in the front matter, that gets overlaid in the corner.
[^thumbnail]: My current theme doesn't make use of the thumbnails, but a previous theme did so I've got a bunch of posts with thumbnails still assigned. And now I've got a use for them again!
I'm sure this could be further optimized by someone who knows what they're doing[^future]. In any case, here's what I did to get this working.
[^future]: Like Future John, perhaps? Past John loves leaving stuff for that guy to figure out.
Here's how I did it.
### New resources
Based on Aaro's suggestions, I started by creating a 1200x600 image to use as the base. I used [GIMP](https://www.gimp.org/) for this.
Based on Aaro's suggestions, I used [GIMP](https://www.gimp.org/) to create a 1200x600 image for the base. I'm not a graphic designer[^web] so I kept it simple while trying to match the theme, font, and colors used on the site.
I'm not a graphic designer[^web] so I kept it simple. I wanted to be sure that the text matched the font used on the site, so I downloaded the [Fira Mono `.ttf`](https://github.com/mozilla/Fira/blob/master/ttf/FiraMono-Regular.ttf) to `~/.fonts/` to make it available within GIMP. And then I emulated the colors and style of the "logo" displayed at the top of the page.
I had to install the Fira Mono font [Fira Mono `.ttf`](https://github.com/mozilla/Fira/blob/master/ttf/FiraMono-Regular.ttf) to my `~/.fonts/` folder so I could use it in GIMP.
![Red background with a command prompt displaying "[runtimeterror.dev] $" in white and red font.](og_base.png)
[^web]: Or web designer, if I'm being honest.
[^web]: Or a web designer, if I'm being honest.
That fits with the theme of the site, and leaves plenty of room for text to be added to the image.
That fits with the vibe of the site, and leaves plenty of room for text to be added to the image.
I also wanted to use that font later for the text overlay, so I stashed both of those resources in my `assets/` folder:
I'll want to also use that font for the text overlay, so 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'.](new_resources.png)
### OpenGraph partial
Hugo uses an [internal template](https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/opengraph.html) 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:
Hugo uses an [internal template](https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/opengraph.html) for rendering OpenGraph properties by default. I needed to import that as a partial so that I could override its behavior. So I dropped the following in `layouts/partials/opengraph.html` as a starting point:
```jinja-html
// torchlight! {"lineNumbers": true}
@ -69,7 +68,7 @@ Hugo uses an [internal template](https://github.com/gohugoio/hugo/blob/master/tp
{{ end }}{{ end }}
```
To use this new partial, I'll add it to my `layouts/partials/head.html`:
To use this new partial, I added it to my `layouts/partials/head.html`:
```jinja-html
{{ partial "opengraph" . }}
@ -84,7 +83,7 @@ which is in turn loaded by `layouts/_defaults/baseof.html`:
```
### Aaro's OG image generation
[Aaro's code](https://aarol.dev/posts/hugo-og-image/) provides the base functionality for what I need:
[Aaro's code](https://aarol.dev/posts/hugo-og-image/) provided the base functionality for what I need:
```jinja-html
{{/* Generate opengraph image */}}
@ -127,12 +126,12 @@ The `resources.Copy` line moves the generated OG image into the post bundle dire
And then the `<meta ... />` lines insert the generated image into the page's `<head>` block so it can be rendered when the link is shared on sites which support OpenGraph.
This is a great starting point for what I want to accomplish, but I'm going to make some changes in my `opengraph.html` partial.
This is a great starting point for what I wanted to accomplish, but made some changes in my `opengraph.html` partial to tailor it to my needs.
### My tweaks
As I mentioned earlier, I want to have basically three recipes for baking my OG images: one for the homepage, one for standard posts, and one for posts with an associated thumbnail. They'll all use the same basic code, though, so I wanted to be sure that my setup didn't repeat itself too much.
As I mentioned earlier, I wanted to have three slightly-different recipes for baking my OG images: one for the homepage, one for standard posts, and one for posts with an associated thumbnail. They all use the same basic code, though, so I wanted to be sure that my setup didn't repeat itself too much.
I'll start with fetching my resources up front, and initializing a `$text` variable:
My code starts with fetching my resources up front, and initializing an empty `$text` variable to hold the description or title:
```jinja-html
{{ $img := resources.Get "og_base.png" }}
@ -140,7 +139,7 @@ I'll start with fetching my resources up front, and initializing a `$text` varia
{{ $text := "" }}
```
If it's the homepage, I'll set `$text` to hold the site description:
For the site homepage, I set `$text` to hold the site description:
```jinja-html
{{- if .IsHome }}
@ -148,7 +147,7 @@ If it's the homepage, I'll set `$text` to hold the site description:
{{- end }}
```
If it's a standard post page, then I want to use the page title instead:
On standard post pages, I used the page title instead:
```jinja-html
{{- if .IsPage }}
@ -156,20 +155,31 @@ If it's a standard post page, then I want to use the page title instead:
{{ end }}
```
Now we get to some more interesting stuff. If the page has a `thumbnail` defined in the front matter, I'll use `$.Resources.Get` to grab the image. Note that the [`resources.Get` function](https://gohugo.io/functions/resources/get/) I used earlier works on global resources, like the image and font I had stashed in the sites `assets/` directory. The [`Resources.Get` method](https://gohugo.io/methods/page/resources/) is used for loading page resources, like the file indicated by the page's `thumbnail` parameter. Since I'm calling this method from within a `with` branch I have to put a `$` in front since the `.` would otherwise refer directly to the `thumbnail` parameter (which isn't a page and so doesn't have the method available). Hugo scoping is kind of wild.
Anyhoo, once I've got the thumbnail I use the [`Fit` image processing](https://gohugo.io/content-management/image-processing/#fit) to scale down the thumbnail (while preserving the aspect ration) and then the [`images.Overlay` function](https://gohugo.io/functions/images/overlay/) to stick it at the top right corner of the `og_base.png` image.
If the page has a `thumbnail` parameter defined in the front matter, Hugo will use `.Resources.Get` to grab the image.
```jinja-html
{{- with .Params.thumbnail }}
{{ $thumbnail := $.Resources.Get . }}
```
{{% notice note "Resources vs resources" %}}
The [`resources.Get` function](https://gohugo.io/functions/resources/get/) (little r) I used earlier works on *global* resources, like the image and font stored in the site's `assets/` directory. On the other hand, the [`Resources.Get` method](https://gohugo.io/methods/page/resources/) (big R) is used for loading *page* resources, like the file indicated by the page's `thumbnail` parameter.
{{% /notice %}}
And since I'm calling this method from inside a `with` branch I have to put a `$` in front of the method. Otherwise, the leading `.` would refer directly to the `thumbnail` parameter (which isn't a page and so doesn't have the method available[^scope]).
[^scope]: Hugo scoping is kind of wild.
Anyhoo, after the thumbnail is loaded, I use the [`Fit` image processing](https://gohugo.io/content-management/image-processing/#fit) to scale down the thumbnail and then call the [`images.Overlay` function](https://gohugo.io/functions/images/overlay/) to *overlay* it near the top right corner of the `og_base.png` image.
```jinja-html
{{ with $thumbnail }}
{{ $img = $img.Filter (images.Overlay (.Process "fit 300x250") 875 38 )}}
{{ end }}
{{ end }}
```
Then I can apply the desired text:
Then I insert the desired text:
```jinja-html
{{ $img = $img.Filter (images.Text $text (dict
@ -185,7 +195,7 @@ Then I can apply the desired text:
### All together now
Now I just need to merge my code in with the existing `layouts/partials/opengraph.html`
After merging my code in with the existing `layouts/partials/opengraph.html`, here's what the whole file looks like:
```jinja-html
// torchlight! {"lineNumbers": true}
@ -244,3 +254,6 @@ Now I just need to merge my code in with the existing `layouts/partials/opengrap
And it works!
![Black background with text "Dynamic Opengraph Images With Hugo", a command prompt "[runtimeterror.dev] $", and colorful hexagon shapes with "HUGO" letters.](og-demo.png)
I'm sure this could be further optimized by someone who knows what they're doing[^future]. I'd really like to find a better way of positioning the thumbnail overlay to better account for different heights and widths. But for now, I'm pretty happy with how it works, and I enjoyed learning more about Hugo along the way.
[^future]: Like Future John, perhaps? Past John loves leaving stuff for that guy to figure out.