Skip to main content
Matt B Blogging

Eleventy, Open Graph images and fun?

This post is maybe way too technical (in a web development sense) - especially compared to my TTRPG stuff, so I won't be sad it you nope out.

In getting this blog up and running again, I wanted to have Open Graph metadata generating properly so that when I shared posts, it looked nice in Mastodon. I know I know, I could actually work on a blog design rather than just using the default blog template.

An example of what an Open Graph preview card might look like

Anyway, I'm using Eleventy to generate my blog. It's pretty neat, but to make it do exactly what I want has been a learning curve. That default template uses the Image plugin, which messes up optimises images and outputs them with new names. That's cool and all, however when I tried to add og:image metadata that points to https://blog.embee.co/games/clem/clem.png but that file had been optimised away… it was fair to say I struggled a lot trying to get it to work. Almost got rid of the Image plugin entirely, thinking it wasn't doing me any favours.

But after whining on Mastodon, some people came to my rescue, and I kept researching. It kinda coincided with all the deepseek AI / LLM hooplah, so I asked that to help too, and while the code it gave me was wrong, it did it confidently, and it gave me a place to start from.

Here's the shortcode that generates an image for use in OpenGraph, and returns the full URL:


import path from "node:path";
import Image from "@11ty/eleventy-img";

export default function (eleventyConfig) {
    eleventyConfig.addShortcode(
        "makeOGImg", 
        async function(src, siteUrl) {
            siteUrl = siteUrl ?? "/";
            // Process the image using the Eleventy Image Plugin
            const metadata = await Image(src, {
                widths: [1200], // Standard Open Graph image size
                formats: ["jpeg"], // Use JPEG for Open Graph compatibility
                outputDir: "./_site/img/og/", // Output directory for OG images
                urlPath: path.join(siteUrl, "img/og/"), // URL path for OG images
                sharpOptions: {
                    quality: 80, // Adjust image quality
                },
            });

            // Get the URL of the generated image
            return metadata.jpeg[0].url;
        }
    );
}

And here's how I used in my base template (using Nunjucks):

<meta property="og:image" content="{% makeOGImg ogimage, metadata.url %}" />

metadata.url is set in my metadata.js file - it's my blog's address: https://blog.embee.co/

I have a base ogimage property set in that template, and it can be overwritten in the data cascade at the directory or individual post level. So that's cool!

And why not show the full top of my base.njk template file so you can see all the Open Graph stuff and default ogimage value in the front-matter.

---
ogimage: ./content/assets/mbbigtitle.svg
ogtype: article
---
<!doctype html>
<html lang="{{ metadata.language }}">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>{{ title }}</title>
		<meta name="description" content="{{ description or metadata.description }}">
		<meta property="og:title" content="{{ title or metadata.title }}" />
		<meta property="og:description" content="{{ synopsis or description or metadata.description }}" />
		<meta property="og:type" content="{{ ogtype }}" />
		<meta property="og:image" content="{% makeOGImg ogimage, metadata.url %}" />
		<meta property="og:url" content="{{ (metadata.url + page.url) | url }}" />	
		<a rel="me" href="https://dice.camp/@mattb" />
		<link rel="alternate" href="feed/feed.xml" type="application/atom+xml" title="{{ metadata.title }}">

OK, that's it. Maybe it will help someone else. Even if that someone else is me in 12 months when I've forgotten everything. Again.

Back to the games!