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:
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:
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.
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.