Auto-Generating Social Preview Images

Nick Radford
Nick Radford
Aug 3, 2021
  • 3 min read

Social preview images are used to entice viewers to read your content when they come across the image on a social network. Websites like Twitter and Facebook read meta tags on your website, and will show the linked image when the right tags are found.

The images (and other metadata) are linked via <meta> tags in the HTML of the page, allowing social networks to parse the title, description, and image to show the potential new reader.

Many people open up Photoshop or some other image editor, put together a graphic, and upload it alongside their content. While there are benefits to doing it this way, for me, automating the process is less time consuming and produces approx. the same results.

Enter Playwright

Playwright is web automation framework from Microsoft, similar to Selenium or Puppeteer. Using Playwright, we are able to build a webpage (easily using our theming, components, fonts, etc) which we will then take a screenshot of and use as our Social Preview Image.

The Preview Image Page

Since I'm using Tailwind, my fonts and primary colors are all set up as classes I can easily use. My preview page expects to be sent the title, date, and slug as query params.

/blog/preview?title=Hello+World&date=2021-08-20&slug=hello-world

Ok, so we have a page that we can use as our image template, but how to take the screenshot?

Capturing a screenshot

The metatags for the social image for blog posts actually hit an API endpoint, which will look up the article by slug then visit the blog/preview page passing the appropriate query params, and finally capture a screenshot using Playwright.

pages/api/preview.ts
import { launchChromium } from "playwright-aws-lambda";

export async function getImage(
  path = "",
  baseUrl = `https://${process.env.VERCEL_URL || "http://localhost:3000"}`
) {
    const url = `${baseUrl}${path}`;

    const browser = await launchChromium({ headless: true });

    const page = await browser.newPage();
    await page.setViewportSize({ width: 1200, height: 628 });
    await page.goto(url, {});

    const buffer = await page.screenshot({ type: "png" });
    await browser.close();

    return buffer;
}

Finally

The API returns the image as a buffer, and is cached on the Vercel Edge Network, which is handily cleared every time my website is deployed. This means if I change the design of my site, I only need update the template, and all future social preview images should be updated accordingly.