Generating OG Images with Headless Browser Screenshots
Why Open Graph images matter for social sharing, how traditional approaches generate them, and how this site uses headless browser screenshots with an async queue to produce pixel-perfect OG images automatically.
What Happens When You Share a Link?
When you paste a URL into Twitter, Slack, or any messaging app, the platform fetches the target page's HTML and looks for Open Graph meta tags:
Without og:image, a shared link is just a plain URL. With it, the link becomes a rich card with a title, description, and a visual preview.
With OG Image → higher click-through rate, more information at a glance.
Without OG Image → the link gets scrolled past.
Traditional Approaches to OG Image Generation
| Approach | How it works | Pros | Cons |
|---|---|---|---|
| Manual design | Export from Figma/Photoshop per page | High quality | Doesn't scale — every page needs manual work |
| Canvas API | Server-side node-canvas compositing | Automated | Weak layout, no CSS support, painful to debug |
| @vercel/og (Satori) | Convert JSX → SVG → PNG | Good Next.js integration, Edge-compatible | Only supports a CSS subset, limited for complex layouts |
| Headless browser | Render the real page in a browser, take a screenshot | Full CSS/JS, pixel-perfect WYSIWYG | Needs a browser runtime, higher resource cost |
The first three approaches try to approximate a browser's rendering. The fourth uses the browser itself.
This Site's Approach: Headless Browser Screenshots
This site uses headless browser screenshots, but instead of managing Chromium instances directly, it delegates the screenshotting to a headless browser cloud API.
The Full Generation Sequence
From the moment an editor saves a page to the OG image being ready:
State Machine
Each page's OG generation status follows a simple finite state machine:
Key Design Decisions
Why not capture the screenshot inside the hook?
The hook runs within the CMS request-response cycle. A screenshot takes 5–10 seconds. Blocking the save operation that long would make editing painful. The hook only marks and dispatches — the actual capture happens asynchronously.
Why a dedicated preview route?
OG images need a specific visual layout: no navigation, no footer, optimized for the 1200×630 aspect ratio. The preview route renders only the page's content blocks without the site shell, and is protected by an authentication header to prevent public access.
Why does the Processor reload the page before capturing?
Queue jobs may execute with a delay. The editor might have made further changes between dispatch and execution. Reloading from the database ensures the screenshot reflects the latest content.
How does the queue switch between environments?
The dispatcher auto-detects the runtime. In production, it uses a cloud queue service with retry and exponential backoff. In development, it uses an in-memory Promise map for inline execution — no external infrastructure needed.