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-worldOk, 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.
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.
Related writing
- Put Some Pants On, YouTube!4 min readA quick browser-extension experiment for hiding YouTube Shorts, built with WXT after digging through YouTube's web component-heavy DOM.
- Module Import Patterns in Javascript2 min readA look at one preferred ordering for JavaScript and TypeScript imports, from third-party modules to local components, utilities, styles, and types.
- Tailwind Just-In-Time Compiler2 min readA short look at Tailwind CSS JIT mode, arbitrary values, enabled variants, and why it made utility-first styling feel smoother.