Generating social images with static site generators
With TokyoDev, this site, my goal is to help international software developers start and grow their career in Japan. Being a developer myself, it’s been tempting to come up with technical solutions to do this. But I’ve realized that rather than writing code, my time is better spent doing things like writing articles, helping companies refine their job postings, and answering readers’ questions. Keeping with this philosophy, the site is quite simple from a technical perspective: it’s a collection of HTML and CSS files built by the static site generator Middleman.
Recently though, I had something I wanted to do that ended up being relatively complicated from a technical perspective: automatically generate “social images” that show up when my articles, job postings, and so on are shared on Twitter, Facebook, and LinkedIn. For an example of this, see the following tweet.
How I generate social images, like the one attached to this tweet, for TokyoDev. https://t.co/hVUYw208xG— Paul McMahon (@pwim) May 29, 2020
Specifying an image to use for a given page is straightforward: just use the Open Graph protocol to specify an
og:image (though Twitter requires some tweaks for an optimal experience). Generating the images themselves is a bit more involved though.
My strategy for generating images
I’d noticed the software developer community site DEV dynamically generates these images. As I remembered they’d open sourced much of their site, it seemed a good place to start. Indeed, I was able to quickly track down how they did it: dynamically construct HTML, and then use the service HTML/CSS to Image API (HCTI API) to convert that to an image.
Overall, the strategy of converting HTML to an image was attractive to me as it meant I could continue to use a single tool for building my site. I could theoretically use a tool like ImageMagick to overlay text on a base image, but that would both require me to learn a new technology, and make it harder to delegate the creation of the image templates to my designer.
Using HCTI API with a static site
With HCTI API, you supply the source HTML via a POST request, and get back a URL for the resulting image. You can supply them this in two ways:
- Use the
htmlparameter to supply actual html to render
- Use the
urlparameter to specify a public html page to render
Since TokyoDev is a static site, I had a finite number of social images that I want to generate at build time. To do this, the first option, passing HTML to the API seemed the right approach, as at build time, I could easily generate that HTML locally.
However, I quickly ran into an issue. I wanted to display the company logo associated with a job posting in the social image. To embed an image like this, I’d need to make it publicly accessible somewhere first, so that HCTI API could fetch it. Doing this was certainly possible, but would make things more complicated.
If I was going to need to make the image publicly accessible at build time anyways, I might as well just make the whole social image page available, and use the
url option instead. Since building and publishing such a page is exactly what Middleman is designed to do, that part was easy. The challenging part was setting a page’s
og:image URL to point to the image generated by HCTI API.
While there’s a number of approaches I could have taken, I thought the easiest approach would be to create a small proxy for HCTI API. When this proxy received a request, it would generate an image with HCTI API and then redirect to that URL. This would mean I could set the
og:image URL to something like
https://hcti-proxy.tokyodev.com/2020/05/29/generating-social-images-with-static-site-generators/, and thus wouldn’t need the social image source page to be publicly accessible at build time.
With a couple hours of work, I successfully had social images for my site. I released the proxy, and thought since it was an interesting topic, I’d write an article about it.
Pursuing a nicer solution
The solution I’d made was good enough. From a rational business perspective that should have been the end of it. But something was still nagging at me: I’d introduced a dynamic component to my static site, and one that was theoretically not needed. The “right” approach would be to generate the images themselves locally. That way, I could do it at build time, ensuring there was one less thing that could go subtly wrong.
Middleman is written in Ruby, and as as Ruby developer I’m familiar with the automated acceptance testing framework capybara. One of its features is the ability to take the screenshot of a page. Another is to use selenium to drive Chrome in headless mode. Put these together, and I’d be able to generate my social images locally while building the site.
The rest is just implementation details. After spending much too much time, I created a middleman extension, Middleman::SocialImage that takes care of the heavy lifting. If you’re interested, check it out! It’s still rough around the edges, so I’d love feedback on it.