New Nov 15, 2024

Generate Open Graph Images with Playwright

More Front-end Bloggers All from dbushell.com View Generate Open Graph Images with Playwright on dbushell.com

My social media posts have been boring on the visuals. Not anymore! I now auto-generate a nice typographic preview. Scroll down to see an example.

This is what shared links to my website looked like before:

Mastodon post with old open graph image

I love my origami crane mascot but three copies is a little excessive!

I did recently fix my fediverse creator tag. If you’re not seeing “More from” on your own links you need to configure Author attribution on Mastodon. It’s easy to miss and separate from website verification.

New Graphics

This is how new shared blog links will appear:

Mastodon post with new open graph image

I’m using a standard Open Graph image size. 1200×630 dimensions are the most common I’ve seen. Mastodon will display the smaller preview if a square image is provided. Bluesky always crops to the 1.91:1 aspect ratio (I think). Twitter is dead and I’ve never used other platforms.

Only my big blog posts get a unique image. My microblog and other pages continue to use the smaller square variation.

Playwright

My static website is built with JavaScript. I was thinking to use Offscreen Canvas to generate images but no server-side runtime supports that API.

Instead I just built a web page! What better way to compose SVG and web fonts? To capture screenshots I’m using the Playwright headless browser.

The Playwright API is super simple:

import { webkit } from "npm:playwright";
const browser = await webkit.launch();
const page = await browser.newPage();
await page.setViewportSize({ width: 1200, height: 600 });
await page.goto("http://localhost/...", { waitUntil: "load" });
await page.screenshot({ type: "png", path: "image/..." });

I have a custom build script and janky web framework that provide a manifest of blog posts. I have a single route for the image generator which I pass a blog post title in the query string.

Playwright can use WebKit, Chromium, and Firefox. I found WebKit to be the fastest to load although there wasn’t much difference. Each screenshot takes around one second to create. This includes passing through Oxipng for compression. This requires oxipng to be installed which I did via Homebrew: brew install oxipng.

await new Deno.Command("oxipng", {
  args: ["-o", "2", filePath],
}).output();

I generate the images locally and push them to Git large file storage. I only need to generate them once. I have a GitHub action that runs later to generate the static site itself, fetch LFS, and deploy to Cloudflare Pages.

Automation

At the moment I must remember to manually generate a new image every time I write a new blog post. I’ll put a hook or check somewhere. Probably.

Eventually I want to build and deploy my website from a Forgejo Action that I self-host. It would be cool to escape GitHub. Whenever I sit down to tackle that task I get distracted.

Kylo Ren meme: I KNOW WHAT I HAVE TO DO, BUT I DON'T KNOW IF I HAVE THE STRENGTH TO DO IT

I’ve definitely accrued some technical debt I should tidy up before I make my website any more complicated. I’m quite happy about these new open graph images though! Long overdue.

Scroll to top