Easy dynamic social sharing image with eleventy

Jan 23, 2023

Introduction

I'm in the process of rebuilding this website using eleventy and I wanted to get fancy with this revamped site.

One feature I wanted was a custom Open Graph social share image for each page on the site. If you're not familiar with Open Graph, check out this explanation. In a nutshell, when you share links to the site on social networks, it should an image to draw people into the page.

Eleventy has a pretty robust ecosystem already and lo and behold, there are already plugins to handle this for you. Great, let's just check them out.

What's out there?

eleventy-plugin-social-img pre-generates cards using a shortcode. I spent about 30 minutes trying to grok the readme and how to use the plugin. I like that you could create an HTML template, but the plugin looked overly complicated for what I needed.

@resoc/eleventy-plugin-social-image only works with Netlify and requires a server component. This site is hosted on GitHub Pages and is fully static. Pass.

eleventy-plugin-social-share-card-generator requires a Cloudinary account. Pass.

eleventy-plugin-og-image looks promising, pre-generates images, and looks pretty easy to use. This could be a runner-up if I ever want to go a different route.

All in all, I wasn't a huge fan of these solutions. There had to be something simpler.

Something simpler

The eleventy team offers various API Services for the public to use. Screenshots is a runtime service that takes a screenshot of a URL and returns that image (and caches it). Bingo!

All I need to do is create a simple template and style it however I'd like for my social sharing card. Apply the template to every page in the site. Then the service can generate screenshots for my blog posts on demand when somebody shares a link to my site.

Creating the card template

To make it easy to manipulate URLs, I would have a mirrored set of paths at https://patricklee.nyc/social/<page URL>. Then I simply embed that URL to the screenshot service API URL and it would return an image.

Example of the social card webpage:

https://patricklee.nyc/social/about

Example of the social card screenshot image generated by the service:

https://v1.screenshot.11ty.dev/https%3A%2F%2Fpatricklee.nyc%2Fsocial%2Fabout%2F/opengraph/_1674507886315

Note: You need to URLEncodeURI the page being passed into the API

I created the simplest webpage possible, you can see the template here.

  • I wanted a simple design with the title of the page, the URL of this site, and a picture of me.
  • I created a 1200 x 630 pixel box, then added my card contents in there. The screenshot service accepts a size parameter opengraph which will take a 1200 x 630 pixel screenshot.

Generating the pages

Next, we need to generate the pages on the build. To do this. I created a page social.md with some basic frontmatter. See the page here.

---
layout: social.liquid
pagination:
  data: collections.all
  size: 1
  alias: post
permalink: "social//"
---

This page uses a combination of collections.all, pagination, and permalink to force eleventy to generate a page for every page on the site under the /social/ namespace.

Now when we want the social image for any page we simply add /social/ to the URL.

For example, the page https://patricklee.nyc/about has a social image at https://patricklee.nyc/social/about.

Updating the Open Graph tags

The last part is to update the Open Graph tags to use this image. In my header, I had my Open Graph social tags available. I just needed to create a URL that would take each page's URL, and append /social to it, then embed it into the screenshot service's URL scheme.

To make things dead simple, I just created a shortcode that would generate this URL when used on a page.

// eleventy.js
eleventyConfig.addShortcode("openGraphScreenshotURL", function () {
  // URL Encode the page
  const encodedURL = encodeURIComponent(
    `https://patricklee.nyc/social${this.page.url}`
  );
  // Generate a cache-busting key for quicker testing
  const cacheKey = `_${new Date().valueOf()}`;
  // Return the screenshot service's URL to add to the open graph tags.
  return `https://v1.screenshot.11ty.dev/${encodedURL}/opengraph/${cacheKey}`;
});

Then I updated my header to use this shortcode under the og:image and twitter:image tags like so.

<!-- header.liquid -->
<meta property="og:image" content="https://v1.screenshot.11ty.dev/https%3A%2F%2Fpatricklee.nyc%2Fsocial%2Fblog%2Feasy-dynamic-social-sharing-image-with-eleventy%2F/opengraph/_1675304807979" />
<meta property="twitter:image" content="https://v1.screenshot.11ty.dev/https%3A%2F%2Fpatricklee.nyc%2Fsocial%2Fblog%2Feasy-dynamic-social-sharing-image-with-eleventy%2F/opengraph/_1675304807980" />

That's all it takes!

Conclusion

The risk here is if the eleventy team decides to sunset the screenshot API, I'll need to switch to a solution that pre-generates the social share images or host the API myself. Considering how little traffic this site gets, I'll take that risk for reduced complexity now.

I like this solution because I came up with it because it is simpler, doesn't require pre-generating all the images upfront, and doesn't require adding more packages. Oh and because I was able to offload a lot of the work to the wonderful devs at eleventy. Thanks, ya'll!