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

  1. Render a subpage for each blog post, showing only the title for starters
  2. 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).
  3. 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:

Screenshot of Twitter's card validator, showing the social image for a blog post, saying "A changelog for my blog posts – a post on annualbeta.com"
It works!

And that's it, folks!

Changelog

  • Dec 29, 2019 Try to clarify my view of aesthetic details vs. cost of JavaScript
  • Dec 29, 2019 Fix the example link for a generated sharing image subpage
Post a comment If you post a tweet with a link to this page, it will appear here as a webmention (updated daily).

Webmentions

  1. Lee Clissett Lee Clissett
    awesome. any chance you'll make the repo for your blog public any time soon?
  2. Søren Birkemeyer 🦊 Søren Birkemeyer 🦊
    That's a yes! It's on the list of things to do next decade. 😅Probably sooner rather than later – I'll ping you. 📣
23 likes
  1. liked by Eugene
  2. liked by Peter Antonius
  3. liked by Juan Fernandes
  4. liked by Matt Biilmann
  5. liked by ⚡️ Mark Buskbjerg ⚡️
  6. liked by Eleventy
  7. liked by Will ᕙ(⇀‸↼‶)ᕗ
  8. liked by Brian Z
  9. liked by Scott McCracken
  10. liked by AJ Klein
  11. liked by M2 🚀
  12. liked by Dan Ciupuliga
  13. liked by jason
  14. liked by Nick
  15. liked by ross
  16. liked by Gaël Poupard
  17. liked by Michael Milz
  18. liked by Jens Grochtdreis
  19. liked by David Hund ✌
  20. liked by Christophe Hollebeke
  21. liked by Nauval Rizky
  22. liked by Manuel Matuzović
  23. liked by Jimmy Hugge
2 reposts
  1. reposted by Will ᕙ(⇀‸↼‶)ᕗ
  2. reposted by Eleventy