Automated OG Image Generation Build Dynamic Social Cards with a Screenshot API
Stop manually creating social preview images. Learn how to generate beautiful OG images on-the-fly using HTML templates and a screenshot API. Code-heavy tutorial with real examples.
You spend hours crafting the perfect blog post, hit publish, and share it on Twitter. And then… a boring gray box. No preview. No image. Just a sad, naked link that nobody wants to click.
Sound familiar? OG (Open Graph) images are the unsung heroes of social sharing. They’re literally the difference between your content getting scrolled past or getting clicked. And if you’re still manually creating them in Figma for every page—we need to talk.
What Are OG Images (And Why Should You Care)?
OG images are the preview cards that appear when you share a link on social media, Slack, Discord, or anywhere that unfurls URLs. They’re defined in your HTML’s <meta> tags:
<meta property="og:image" content="https://yoursite.com/og/your-page.png" />
<meta property="og:title" content="Your Awesome Title" />
<meta property="og:description" content="A compelling description" />
The problem? Creating these images manually doesn’t scale. Got 100 blog posts? That’s 100 images. Got dynamic product pages? Good luck keeping up.
The Solution: Generate OG Images On-the-Fly
Here’s the move: instead of pre-generating images, we’ll render an HTML template and screenshot it in real-time using a screenshot API. The flow looks like this:
- Create an HTML template for your OG card
- Host it with dynamic parameters (title, author, etc.)
- Point your
og:imagemeta tag at the screenshot API - Done. Every page gets a unique, beautiful preview card.
Step 1: Build Your OG Template
First, create a simple HTML page that renders your social card. This can be a dedicated route in your app or a static HTML file.
<!-- /og-template?title=Hello%20World&author=John -->
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 1200px;
height: 630px;
display: flex;
flex-direction: column;
justify-content: center;
padding: 60px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: system-ui, sans-serif;
color: white;
}
h1 {
font-size: 64px;
font-weight: 800;
line-height: 1.1;
margin-bottom: 24px;
}
.author {
font-size: 28px;
opacity: 0.9;
}
.logo {
position: absolute;
bottom: 40px;
right: 60px;
font-size: 24px;
font-weight: 600;
}
</style>
</head>
<body>
<h1 id="title">Your Title Here</h1>
<p class="author">by <span id="author">Author Name</span></p>
<div class="logo">yoursite.com</div>
<script>
const params = new URLSearchParams(window.location.search);
document.getElementById('title').textContent = params.get('title') || 'Untitled';
document.getElementById('author').textContent = params.get('author') || 'Anonymous';
</script>
</body>
</html>
The key here: 1200×630 pixels. That’s the magic size for OG images. Twitter, Facebook, LinkedIn—they all play nice with this dimension.
Step 2: Screenshot It with an API
Now for the fun part. Instead of spinning up Puppeteer on your server (and dealing with all that headless Chrome drama), use a screenshot API to render your template.
Here’s a Node.js example showing the general pattern:
// Generate OG image URL using a screenshot API
function getOgImageUrl(title, author) {
const templateUrl = encodeURIComponent(
`https://yoursite.com/og-template?title=${encodeURIComponent(title)}&author=${encodeURIComponent(author)}`
);
// Most screenshot APIs follow this pattern:
// Pass your template URL + dimensions, get back an image
return `https://your-screenshot-api.com/capture?` +
`url=${templateUrl}` +
`&width=1200` +
`&height=630` +
`&format=png`;
}
Step 3: Wire It Into Your Meta Tags
In your page’s <head>, dynamically set the OG image:
// Next.js example (pages/_app.js or layout.tsx)
import Head from 'next/head';
export default function BlogPost({ post }) {
const ogImage = getOgImageUrl(post.title, post.author);
return (
<>
<Head>
<meta property="og:image" content={ogImage} />
<meta property="og:title" content={post.title} />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:image" content={ogImage} />
</Head>
<article>{/* Your content */}</article>
</>
);
}
Step 4: Add Caching (Important!)
You don’t want to hit the API every time Twitter’s crawler checks your page. Set up a caching layer:
// Edge function example (Vercel/Cloudflare)
export default async function handler(req) {
const { title, author } = req.query;
// Call your screenshot API
const screenshotUrl = buildScreenshotUrl({
url: `https://yoursite.com/og-template?title=${title}&author=${author}`,
width: 1200,
height: 630,
format: 'png'
});
const response = await fetch(screenshotUrl);
const imageBuffer = await response.arrayBuffer();
return new Response(imageBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=86400, s-maxage=604800', // Cache for a week
},
});
}
Now your meta tag points to your own endpoint, which caches the result:
<meta property="og:image" content="https://yoursite.com/api/og?title=My%20Post&author=John" />
Pro Tips 🔥
- Use web fonts: Google Fonts work great. Just make sure they’re loaded before the screenshot fires.
- Add your branding: Logos, gradients, patterns—make it recognizable in a feed.
- Keep text big: Social cards are often viewed as thumbnails. 48px+ for titles.
- Test with validators: Use Twitter’s Card Validator and Facebook’s Debugger to preview.
Real-World Examples
This pattern is used by some of the best dev blogs out there:
- Vercel/Next.js: Their OG images include post title, date, and reading time
- GitHub: Repo cards show stars, forks, and description
- Dev.to: Article cards with author avatar and reaction count
You can do the same thing in 20 minutes with a screenshot API. No infrastructure to maintain, no Chromium binaries to wrangle.
The Complete Flow
// Full working example with error handling
async function generateOgImage(title, author, category) {
const templateParams = new URLSearchParams({
title: title.slice(0, 60), // Truncate long titles
author,
category: category || 'Blog'
});
const templateUrl = `https://yoursite.com/og-template?${templateParams}`;
try {
// Adapt this to your screenshot API of choice
const response = await fetch(SCREENSHOT_API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SCREENSHOT_API_KEY}`
},
body: JSON.stringify({
url: templateUrl,
viewport: { width: 1200, height: 630 },
format: 'png',
waitUntil: 'networkidle' // Wait for fonts/images to load
})
});
if (!response.ok) throw new Error('Screenshot failed');
return await response.arrayBuffer();
} catch (error) {
console.error('OG generation failed:', error);
// Return a fallback image
return fetch('https://yoursite.com/default-og.png').then(r => r.arrayBuffer());
}
}
Why Use a Screenshot API Instead of Self-Hosting?
You could run Puppeteer yourself. But here’s what that looks like:
- 600MB+ Docker images with Chromium
- Memory spikes that crash your server
- Zombie processes haunting your containers
- Font rendering issues across different environments
Or you could just… not. A screenshot API handles all of that, and you pay per screenshot instead of per server hour. For most sites, that’s dramatically cheaper.
Get Started
Ready to make your social shares look professional? Check out Snapopa for a screenshot API built specifically for this use case. You’ll have dynamic OG images running in under 30 minutes.
Your Twitter game is about to level up. 📈
Install Our Extensions
Add IO tools to your favorite browser for instant access and faster searching
恵 Scoreboard Has Arrived!
Scoreboard is a fun way to keep track of your games, all data is stored in your browser. More features are coming soon!
Must-Try Tools
View All New Arrivals
View AllUpdate: Our latest tool was added on Feb 4, 2026
