Auto-Generate Social Media Preview Images with a Screenshot API
When someone shares your link on Twitter, LinkedIn, Slack, or Discord, the platform fetches your Open Graph image. A compelling image makes people click. A missing one gets scrolled past.
Generating unique images for every page used to mean Canvas APIs, ImageMagick wizardry, or paying a designer. There's a simpler way: render an HTML template with a screenshot API.
Why HTML templates beat Canvas/ImageMagick
- You already know HTML/CSS — no new API to learn
- Fonts work automatically — load Google Fonts, Inter, whatever you want
- Flexbox, grid, gradients — all supported because it's real Chromium
- Emojis render correctly — full Unicode support, including skin tone variants
- No pixel math — let the browser layout engine figure out positioning
The template pattern
Design your card as a standalone HTML page sized 1200×630 (the Open Graph standard):
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
.card {
width: 1200px;
height: 630px;
padding: 80px;
box-sizing: border-box;
background: linear-gradient(135deg, #1e3a8a 0%, #2563eb 100%);
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.title { font-size: 64px; font-weight: 800; line-height: 1.1; }
.footer { display: flex; justify-content: space-between; align-items: center; }
.logo { font-size: 24px; font-weight: 700; }
.author { font-size: 20px; opacity: 0.8; }
</style>
</head>
<body>
<div class="card">
<div class="title">{{title}}</div>
<div class="footer">
<div class="logo">yourbrand.com</div>
<div class="author">{{author}}</div>
</div>
</div>
</body>
</html>
Render it on every publish
When a new blog post is published, call the API once and cache the result:
import Client from "screenshotapis";
const client = new Client(process.env.SCREENSHOT_API_KEY);
async function generateOgImage(post) {
const html = template
.replace("{{title}}", post.title)
.replace("{{author}}", post.author);
const { data } = await client.screenshot({
html,
format: "png",
device_scale_factor: 2, // retina
});
await s3.putObject({
Bucket: "og-images",
Key: `posts/${post.slug}.png`,
Body: data,
ContentType: "image/png",
});
return `https://cdn.yourbrand.com/og-images/posts/${post.slug}.png`;
}
Add the meta tags
Include the generated image URL in your page's head:
<meta property="og:image" content="https://cdn.yourbrand.com/og-images/posts/my-post.png"> <meta property="og:image:width" content="1200"> <meta property="og:image:height" content="630"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:image" content="https://cdn.yourbrand.com/og-images/posts/my-post.png">
Testing your OG images
Before publishing, verify your image renders correctly on each platform:
- Twitter — Card Validator
- Facebook/LinkedIn — Sharing Debugger
- General — OpenGraph.xyz
Pro tips
- Cache aggressively — OG images rarely need to regenerate
- Use
device_scale_factor: 2for crisp retina images - Keep titles under 80 characters so they fit in 2 lines at 64px font
- Test with actual long titles — never design with "Lorem ipsum"
- For user-generated content, escape HTML to prevent XSS in your template
Generate beautiful OG images from HTML templates
Get your API key — free