Dynamic Social Sharing Images with Eleventy
One year ago, I read Dynamic Social Sharing Images by Drew McLellan on 24ways and got super excited with the simplicity and ingeniousness of the technique.
Because it's the holidays and because I love tinkering, I sat down to try and replicate this with Eleventy. I wanted to pretty much follow Drew's steps and
- Render a subpage for each blog post, showing only the title for starters
- Style the subpage with CSS so it can be turned into a 600 × 315 pixel image (the correct aspect ratio for Facebook’s recommended image size).
- Screenshot all new subpages with Puppeteer
It turns out Eleventy has just the right tool for step 1: Pagination.
I created a new template file (Nunjucks) in the src
folder:
---
pagination:
data: collections.article
size: 1
alias: article
permalink: /blog/{{ article.fileSlug }}/og-image/
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic social sharing image for “{{ article.data.title }}”</title>
<link rel="stylesheet" type="text/css" href="{{ '/css/og-image.css' | url }}" />
<script src="{{ '/js/og-image.js' | url }}" defer></script>
<meta name="robots" content="noindex,nofollow">
<link rel="icon" type="image/png" href="{{ '/favicon-32x32.png' | url }}" sizes="32x32">
</head>
<body>
<figure class="ab-og-image">
<h1 class="h1">{{ article.data.title }}</h1>
<p>a post on <a href="https://annualbeta.com">annualbeta.com</a></p>
</figure>
</body>
Enabling pagination in the frontmatter tells Eleventy to loop over all items in the "article" collection (which happen to be my blog posts), use article
as an alias, create one new page for each article and save it to a folder named og-image
. The magic here is that {{ article.fileSlug }}
will return the folder name of the current article, so Eleventy will always write the new index.html
into the right parent folder.
I decided to not even bother with a layout here because the page only needs the bare minimum of markup to function. The external stylesheet contains the CSS needed to format the text, size the figure correctly and add a subtle amount of background noise.
Because the end product would be a static image at a fixed size, I decided to satisfy my typographic vanity and also wrote a bit of JavaScript to prevent single words on the last line (to be clear: I usually prioritize having less JavaScript over typographic detail for production websites, but not always – sometimes the aesthetic benefits outweigh the cost of JavaScript). Andy Bell's TypeMate was just the right plugin for that.
You can see the result for each blog post (like this one) if you append og-image/
to the URL.
That left only step 3. I like and use Gulp a lot, so I ported Drew's solution over to my build pipeline. The screenshots are saved to the respective article's folder in src
, so they get copied over to the dist
folder every time Eleventy runs. I chose the article's URL slug as the file name, prefixed with "og-img-". That meant I could now add social meta tags to my blog post template:
<meta property="og:title" content="{{ title }}">
<meta property="og:image" content="{{ page.url | url | absoluteUrl(metadata.url) }}og-img-{{ title | slug }}.png">
<meta name="twitter:card" content="summary_large_image">
{# and a few more optional ones, like publication date, twitter user, … #}
A quick test with the Twitter card validator proves that everything is working:
And that's it, folks!