How to Dynamically Generate Open Graph Images at the Edge
When links are shared on social media networks like Twitter, LinkedIn, or Slack, these platforms read the HTML meta tags to display a preview card containing a title, description, and an Open Graph (OG) image. A customized, dynamic image that displays page-specific data (like the article title, author avatar, and read time) dramatically improves click-through rates.
However, pre-rendering static images for thousands or millions of pages is highly inefficient. The modern solution is to generate these images dynamically on request using serverless edge computing, rendering visual graphics in under 100 milliseconds and caching them globally.
The Engine: Satori and WebAssembly
Historically, dynamic image generation required running headless browsers like Puppeteer on virtual machines. This architecture is slow, heavy, and expensive to scale.
Today, we can run lightweight graphics engines at the edge. The modern stack consists of:
- Satori: A library created by Vercel that converts JSX/HTML and CSS layouts into SVG vectors. It runs in serverless edge runtimes because it does not require a browser engine.
- Resvg-Wasm: A WebAssembly binary of the high-performance Rust library
resvg, which compiles SVG vectors into PNG images. - Edge Caching: Using HTTP headers and edge key-value stores to ensure each dynamic image is generated only once and served instantly to subsequent requests.
Step-by-Step Implementation
Let's build a Cloudflare Worker that intercepts image requests, parses search parameters, generates an SVG layout using JSX, and converts it into a PNG file. Since edge runtimes lack access to system font directories, we must fetch a font file (like Inter) as a binary buffer to render the text.
import satori from 'satori';
import { initWasm, Resvg } from '@resvg/resvg-wasm';
// Load the resvg WebAssembly module
import resvgWasm from '@resvg/resvg-wasm/index.wasm';
let wasmInitialized = false;
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const title = url.searchParams.get('title') || 'Default Title';
const author = url.searchParams.get('author') || 'Anonymous';
// 1. Initialize WebAssembly compiler once
if (!wasmInitialized) {
await initWasm(resvgWasm);
wasmInitialized = true;
}
// 2. Fetch font buffer
const fontResponse = await fetch('https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZJhjp-Ek-_y.woff');
const fontData = await fontResponse.arrayBuffer();
// 3. Generate SVG using Satori
const svg = await satori(
{
type: 'div',
props: {
style: {
display: 'flex',
flexDirection: 'column',
width: '1200px',
height: '630px',
backgroundColor: '#0f172a',
padding: '80px',
fontFamily: 'Inter',
color: '#f8fafc',
justifyContent: 'space-between',
},
children: [
{
type: 'h1',
props: {
style: { fontSize: '64px', fontWeight: 'bold', lineHeight: 1.2 },
children: title,
},
},
{
type: 'div',
props: {
style: { display: 'flex', alignItems: 'center', fontSize: '24px', color: '#94a3b8' },
children: `By ${author}`,
},
},
],
},
},
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: fontData,
weight: 700,
style: 'normal',
},
],
}
);
// 4. Convert SVG to PNG using Resvg
const resvg = new Resvg(svg);
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
// 5. Return PNG with caching headers
return new Response(pngBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
},
};
Layout Limitations and Design Constraints
Designing templates for Satori requires adhering to specific layout limitations. Satori relies on Facebook's Yoga layout engine under the hood. It only supports Flexbox styling, meaning layout strategies like CSS Grid, float, or custom grid layouts will fail to render.
Properties like box-shadow, text-transform, and complex SVG path tags are either partially supported or completely ignored. When styling, construct nested flex divs and use percentage or pixel spacing parameters. Testing your JSX layouts locally using Satori's development playground before pushing code to the edge server prevents broken social card renders in production.
Performance Optimization Strategies
While compiling SVG to PNG at the edge is fast, loading font files and rendering JSX on every single request can degrade latency if your site receives heavy traffic. To achieve sub-50ms load times, apply these optimizations:
- Font Subsetting: Font files can be large (often over 1MB). Use tools like
glyphhangerto create a subset of the font containing only the characters you plan to render, reducing the font buffer size to less than 20KB. - Edge Cache Integration: Configure Cloudflare's Cache API to intercept the request. If the requested URL with the specific query parameters matches an already cached image, return the image directly from the edge cache, bypassing the compute logic completely.
- Tiered Caching: Leverage CDN cache hierarchy to cache assets at the regional level, ensuring requests never travel back to your origin edge function unless the cache key has expired or been explicitly purged.
To implement purging, use cache tag headers (like Cache-Tag) in your responses. When an article is updated, send a PURGE request to your CDN's API using that tag. This invalidates only the cached OG images associated with that specific post, allowing fresh images to be rendered automatically on the next share request while maintaining high cache hit ratios for the rest of your site.
Dynamic Asset Generation with Bramsley
Implementing dynamic, edge-rendered asset pipelines requires resolving complex WebAssembly compilation tasks, font caching architectures, and serverless latency tuning. At Bramsley Digital Studio, we help organizations design and deploy high-performance graphic generation engines directly at the global edge.
By combining micro-optimized Wasm runtimes with advanced edge routing and tiered caching, We ensure your dynamic social previews, PDF certificates, and customized banner imagery render instantly, keeping your site fast and optimized. Partner with us to deploy state-of-the-art dynamic asset pipelines today.