Some time last year I thought it might be nice to have opengraph preview images on my blog posts, so if I posted them to Mastodon theyād show a cool little preview image and more people would click on them.
Itās easy enough, you just need something like this in the <head>
:
<meta property="og:image" content="https://www.hardscrabble.net/img/preview/2024-10-03-colors.png">
Because I use Jekyll to generate my blog, it wasnāt too hard to add a little conditional logic that includes that meta element if a post specifies a preview image filename in its front matter.
But itās sort of annoying to have to prepare an image whenever you want to blog something. I donāt necessarily have time for all that.
Then, yesterday, I was poking around on Bluesky and saw this post:
I'm starting my 2024 #blogvent series where I post a blog a day in December! Blogvent day 1 is about fighting spam in your open source repos: cassidoo.co/post/oss-int...
— Cassidy (@cassidoo.co) December 1, 2024 at 4:07 PM
[image or embed]
And I got so jealous of how nice and clean that preview image is, and I had a few thoughts:
- Sigh I should blog more
- Dang can you imagine blogging that much?
- No, I mean, it would be so annoying to generate all those preview images
- Wait, how did she manage to auto-generate the preview images with text for each blog post??? Thatās so cool!!!
I saw that her blog post is open source and I tracked down the lines of code, which look sort of like this:
---
const { title, description, image = "/home-blog-card.png" } = Astro.props;
---
<head>
<meta property="og:image" content={new URL(image, Astro.url)} />
</head>
I sat and stared at that for several minutes, trying to puzzle out how it works. Iāve never used Astro, but it seems very powerful. I look at the docs.
Eventually I realized that actually itās just a static image, and it isnāt dynamically changing the text for each blog post. I went back to her page and scrolled down past several links, and I saw that they all have the same text.
Lol.
But hey, I thought, maybe it is possible to generate custom images with text in them? And if it is, maybe it is possible to hook that into the jekyll build process?
And indeed it is. And Iāve done a somewhat basic version of that. And hereās how you can do that too.
First, create a plugin by creating a file called _plugins/opengraph.rb
. Ruby files in the _plugins
folder are loaded during the build process.
Within that plugin, register some jekyll hooks:
Jekyll::Hooks.register :posts, :pre_render do |post|
unless post.data["preview_image"]
preview = GeneratePreview.new(post)
preview.write
post.merge_data!({"preview_image" => preview.path }, source: "opengraph plugin")
end
end
Jekyll::Hooks.register :site, :after_init do
FileUtils.mkdir_p "tmp/img/preview"
end
Jekyll::Hooks.register :site, :post_write do
Dir.glob("tmp/img/preview/*").each do |path|
FileUtils.cp path, "_site/img/preview"
end
end
The idea here is that for any post that doesnāt already have a preview image specified in the front matter, weāll
- generate a preview image and write it to a folder
- modify the in-memory post object to know that it has a preview image with a particular filename. Because this is a
:pre_render
hook, the HTML for that post has not yet been written to disk, so itās not too late to modify its metadata
And then, after the whole site has been written, copy all of those generated image files into place.
In this design, I donāt need to commit all of the generated files to the repository, I just generate them at build time. I need to regenerate them each time I deploy. This is a bit slow and ineffecient. I might decide to just commit them down the line.
The actual image generation code is sort of hacky. Itās a bit of ruby glue code that shells out to the venerable imagemagick CLI tool. It executes commands like this:
magick \
-background "#f2d8b2" \
-font "Helvetica" \
-fill "#248165" \
-size 1200x630 \
-gravity SouthWest \
"caption:The\ easiest\ way\ to\ indent\ paragraphs\ online,\ not\ that\ you\ necessarily\ should" \
tmp/img/preview/2012-03-21-indenting-paragraphs-online.png
Which produces images like this:
Kind of obnoxious right? A little brat maybe? You tell me.
Getting this to run in GitHub Actions presented a few challenges. The ubuntu-latest
machine where the builds run had imagemagick installed, but it was imagemagick 6, not 7. sudo apt-get install -y imagemagick
just complained that hey, itās already installed baby, nothing for me to do. But I had gotten all of this working with imagemagick 7 and I wasnāt about to redo anything.
Once I figured out how to install imagemagick 7, I had the next issue, which was that no fonts were installed on the machine, at least as far as imagemagick could tell. After installing the gsfonts package, it could see some fonts, but only the off brand, Linux versions of them. By running magick -list font
in the CI context, I could see what those were. Eventually I worked out that something called Nimbus-Sans-L is basically knockoff Helvetica, which was what I had randomly picked locally. Great. Close enough. Perhaps you can tell but I have never really been a Font Guy. But I wanted this to work in both contexts, locally and in GitHub Actions. To work around that, I added some conditional logic to the ruby script to default the font to Helvetica, but allow overriding it via an environment variable.
And with thatā¦ everything was working, and I went to bed, criminally late, having forgotten what I originally had been planning to blog about. I remembered over lunch today and finished that post. And now hereās this.
Hereās the commit with all of the code, if youāre curious. It kind of works.