Skip to content

OG Screenshot Worker

Generate dynamic Open Graph images using Cloudflare Browser Rendering.

This Cloudflare Worker takes live screenshots of pages on soderlind.no and serves them as OG images for social media previews.

How It Works · Setup · Configuration · Usage · Cache Warmup


When someone shares a link to soderlind.no/plugins/wp-loupe/, social platforms fetch https://og.soderlind.no/plugins/wp-loupe.png. The worker caches on two tiers and only renders on a full miss:

  1. Edge cache (Cache API) — Returns the image from the colo-local edge cache if present (fastest, no KV read)
  2. KV cache — On an edge miss, returns the screenshot from KV (durable, cross-colo) and warms the edge cache
  3. Launches headless browser — On a full miss, uses Cloudflare Browser Rendering
  4. Takes screenshot — Captures the page at 1200×630 pixels (OG standard)
  5. Populates both caches — Stores in KV for 7 days and in the edge cache (non-blocking, via waitUntil)
  6. Returns PNG — Serves the image with proper headers

  • Cloudflare Workers Paid plan (required for Browser Rendering)
  • wrangler CLI installed
Terminal window
cd workers/og-screenshot
npx wrangler kv namespace create CACHE

Copy the returned id into wrangler.toml.

Terminal window
npm install
npx wrangler deploy

In Cloudflare Dashboard → DNS → Add record:

TypeNameContentProxy
AAAAog100::Proxied

This routes og.soderlind.no to the worker.

In your Astro site, set the og:image URL:

---
const ogSlug = Astro.locals.starlightRoute?.id || slug || 'index';
const ogImage = `https://og.soderlind.no/${ogSlug}.png`;
---
<meta property="og:image" content={ogImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

name = "og-screenshot"
main = "src/index.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]
[browser]
binding = "BROWSER"
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"
[[routes]]
pattern = "og.soderlind.no/*"
zone_name = "soderlind.no"

src/index.ts contains the worker code that handles requests, checks caches, and generates screenshots. It uses the Cloudflare Browser Rendering API to take screenshots of pages and serves them as PNG images.

Source code is available at GitHub.

BindingTypeDescription
BROWSERBrowserCloudflare Browser Rendering binding
CACHEKV NamespaceScreenshot cache storage

https://og.soderlind.no/{slug}.png
PageOG Image URL
Homepagehttps://og.soderlind.no/index.png
Abouthttps://og.soderlind.no/about.png
WP Loupehttps://og.soderlind.no/plugins/wp-loupe.png
AI Routerhttps://og.soderlind.no/ai/ai-router.png
  • X-Cache: HIT — Served from cache (edge Cache API or KV). Cloudflare’s cf-cache-status distinguishes an edge HIT from a MISS that was served by the worker from KV.
  • X-Cache: MISS — Fresh screenshot generated, then written to both KV and the edge cache.
Terminal window
# Check if working
curl -I https://og.soderlind.no/about.png
# Verify cache hit on second request
curl -I https://og.soderlind.no/about.png | grep -i X-Cache

A GitHub Action runs weekly to warm the OG image cache, ensuring fast social media previews.

The workflow runs every Sunday at 06:00 UTC:

.github/workflows/warm-og-cache.yml
name: Warm OG Image Cache
on:
schedule:
- cron: '0 6 * * 0' # Weekly on Sunday at 06:00 UTC
workflow_dispatch:
jobs:
warm-cache:
runs-on: ubuntu-latest
steps:
- name: Fetch sitemap and warm OG cache
run: |
curl -s https://soderlind.no/sitemap.xml | \
sed -n 's/.*<loc>https:\/\/soderlind\.no\/\([^<]*\)<\/loc>.*/\1/p' | \
sed 's/\/$//' | \
while read slug; do
[ -z "$slug" ] && slug="index"
curl -s -o /dev/null "https://og.soderlind.no/${slug}.png"
done

Trigger the workflow manually from GitHub Actions, or run locally:

Terminal window
cat sitemap.xml | \
sed -n 's/.*<loc>https:\/\/soderlind\.no\/\([^<]*\)<\/loc>.*/\1/p' | \
sed 's/\/$//' | \
while read slug; do
[ -z "$slug" ] && slug="index"
curl -s -o /dev/null -w "%{http_code} ${slug}\n" "https://og.soderlind.no/${slug}.png"
done

If screenshot fails (timeout, browser error), the worker falls back to the static Satori-generated OG image at /og/{slug}.png.


Browser Rendering is included in Workers Paid plan:

  • First 1,000 browser sessions/month free
  • $0.02 per additional session

With 7-day caching, typical documentation sites stay well within $5 tier.