Going Static Part 3

Blog images for lazy people with writenow

10 November 2018

In the first post in this series I promised I'd write in a future post about automating social media images for blog posts, and that day has now arrived 🎉 . What started off as a seemingly simple additional feature ultimately turned into an npm package for a CLI app - but let's not get ahead of ourselves, I'll come to that in a moment.

The problem

I gave some background on what I wanted to do with images in my first post about Eleventy. I wanted to reduce file size and improve loading times, and prioritise the real content of posts: the text. But I also recognise that an embedded link on social media is much more likely to attract attention and interest if it has a relevant image.

Blogging tools like WordPress and Ghost generally take the 'feature image' or, failing that, the first image in a post if there is one, and inject that into Open Graph and Twitter meta tags in the <head> of the page. We explored this process with other types of metadata like the title and subject/s of a post, in Going Static Part 1. Both Open Graph and Twitter Cards have a meta tags for an image as well as a separate one for a description of the image (as opposed to the description of the article). The description is turned into alt text when a link is embedded in Twittter, Facebook, Mastodon or something else that uses Open Graph, enabling people browsing with screen readers to 'see' the embedded image. Taking this full circle a bit, when I made my last theme for Ghost and my WordPress theme for the newCardigan website I tried to pull the existing alt text from post feature images into the Open Graph and Twitter Card image description tags automatically, but I couldn't work out how to do it.

With Eleventy, I have a lot more control over how everything is put together. What I wanted to do was, conceptually, fairly straightforward:

  1. Use an API to programatically retrieve a URL for a freely-licensed image for each post, based on relevant keywords
  2. Inject that image url into the Open Graph and Twitter meta tags
  3. If possible, inject a description of the image into the Open Graph and Twitter image description tags.

Using the Unsplash API

Initially, being a librarian, I looked at Trove and British Library, but I concluded that I wasn't really going to get what I wanted, and my experience with APIs like these is that the images can be a bit hit and miss in terms of their suitability for blog post feature images. Indeed, the British Library image API is talked about all over the web, but none of the links seem to work anymore, and the British Library Labs project seems to have been reduced to three people so under-resourced that they have to document their existence in a Google Doc. As I explored my options, I realised that I'd already been using the service I wanted, because Ghost integrates with Unsplash. I'm not really sure about their business model, so it's likely I'll have to find something else in a few years when the vulture capital runs out, but in the meantime Unsplash offers high quality photos, freely licensed (attribution appreciated but not required), and accessible via a well-documented, free API. It was exactly what I wanted.

I wrote a simple nodejs script using inquirer to build frontmatter for a post (asking for title, subtitle, tags, and summary text), then call the Unsplash API using a randomly selected word from the title as the query, and insert the URL as the 'image'. The Unsplash has an incredibly convenient call for this purpose: you can call photos/random?query=puppies for a random photo of puppies, or photos/random by itself to just grab a completely random photo. This allows us to use the second option (without a query keyword) as a fallback if the first call comes back with nothing - which is entirely possible when using a random word as the query! The other cool thing about the Unsplash API is that it automatically returns a description of the photo, as well as three different image URLs depending on what size you want. Putting all of this together, I was able to make a script that will always return a photo - it just isn't guaranteed to always be relevant to the post. Here's the frontmatter for this post, for example, which was generated by my script:

---
layout: post
title: Going Static Part 3
subtitle: Blog images for lazy people with writenow
author: Hugh Rundle
tags: ['eleventy','coding','metadata','post']
summary: How I solved the problem of showing images in social media links without rendering them on my blog pages.
image:
  photo: https://images.unsplash.com/photo-1462157948078-cbc0cd80e4d7?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&ixid=eyJhcHBfaWQiOjM3NzgyfQ&s=6861c273b9a5ce72e0f7c34663549be6
  description: person in green grass field
---

In this case, in an unintentially meta example, the keyword used to retrieve an image was images.

Using images in social media cards

So now we have all this stuff in the frontmatter, what do we do with it? I showed you what happens with most of these values when we looked at what goes in the <head>. There were only a couple of meta tags missing from that post, and they're the ones we add now:

{%raw%}
<meta name="twitter:image" property="og:image" content="{{image.photo}}">
<meta name="twitter:image:alt" property="og:image:alt" content="{{image.description}}">
{%endraw%}

Conveniently, because Twitter uses the standard html name attribute and Open Graph uses the rdf property attribute, we can deal with images for both of them in the same element. Effectively what we're doing is hotlinking to the image stored on Unsplash's server - which is actually what Unsplash prefers. Here's the resulting image embedded in the Twitter post when I tweeted a link to my last blog post:

<img class="u-block" data-src="https://pbs.twimg.com/card_img/1058936770010722304/XnBQx9y6?format=jpg&amp;name=144x144_2" alt="sliced strawberries on pan cake" src="https://pbs.twimg.com/card_img/1058936770010722304/XnBQx9y6?format=jpg&amp;name=144x144_2">

Twitter has changed the URL for the image, but you can see that they use the alt text provided in my meta tag. Problem solved!

writenow

Eleventy is really great for processing markdown and turning it into full html pages using templates, but it has no built-in way to actually publish those pages. That's absolutely fine, because it's not Eleventy's job to be a publishing platform. But it still left me with a problem: how to get my shiny new blog post drafts from my laptop onto my blog server. I'd heard of a tool called rsync, so I had a look at it and immediately wondered why I hadn't been using it for years. rsync synchronises the files between two different places (directories on the same machine, locations in the same network, or in this case a local directory and another directory on a remote machine). It does this using various fancy techniques to minimise the amount of data moving between the two locations: so it's really good for doing regular backups where you probably only want to change a few files, or for publishing just the latest changes to a website - which is what we do at work to synchronise the staging and production websites, and what I want to do in this case as well. The other convenient thing for me was that rsync comes standard with MacOS so I didn't even need to download it.

Initially I just used rsync by itself, but I now had two command-line tasks related to my blog (in addition to running eleventy pre-processing), and started to think about pulling them into the one tool. I was also using static-serve to check my processed posts before publishing them. Even so, if I was going to push things to my server, I was a bit worried I might accidentally type the wrong command and wipe everything. Maybe I needed a backup system 🤔 . Eventually all of this turned into a command line utility I called writenow. Publishing it to npm yesterday was terrifying, but also exciting: it's not a hugely complicated application, but since it's so helpful for me, I'm sure it will be helpful for others. You can get started by running npm i writenow -g, or check out the code on GitHub.