Every time you share a link on Twitter, Slack, or Discord, the preview image matters. A good OG image gets clicks. A missing or broken one gets scrolled past.
Most solutions require you to design templates in Figma, set up headless Chrome yourself, or use expensive SaaS tools that charge $50+/month. Here's a simpler approach: use a screenshot API to turn any HTML into a perfect OG image.
The Problem
You have a blog, SaaS app, or documentation site. You need unique OG images for every page. Manually creating them is not an option when you have hundreds of URLs.
The Solution: Screenshot API + HTML Template
The idea is simple:
- Create an HTML page that renders your OG image (title, description, branding)
- Call a screenshot API to capture it as a 1200x630 PNG
- Serve that as your og:image
Here's a working example using GrabShot:
curl "https://grabshot.dev/v1/screenshot?url=https://yoursite.com/og/my-post&width=1200&height=630&format=png" \
-H "x-api-key: YOUR_KEY" \
-o og-image.png
That's it. One API call, one image.
Step 1: Create an OG Template Page
Create a simple HTML page at /og/:slug on your site:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
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: 48px; margin: 0 0 20px; }
p { font-size: 24px; opacity: 0.9; }
.logo { margin-top: auto; font-size: 20px; opacity: 0.7; }
</style>
</head>
<body>
<h1>Your Post Title Here</h1>
<p>A brief description that makes people want to click</p>
<div class="logo">yoursite.com</div>
</body>
</html>
Step 2: Automate with a Build Script
const fetch = require('node-fetch');
const fs = require('fs');
async function generateOG(slug, outputPath) {
const url = `https://yoursite.com/og/${slug}`;
const apiUrl = `https://grabshot.dev/v1/screenshot?url=${encodeURIComponent(url)}&width=1200&height=630&format=png`;
const response = await fetch(apiUrl, {
headers: { 'x-api-key': process.env.GRABSHOT_KEY }
});
const buffer = await response.buffer();
fs.writeFileSync(outputPath, buffer);
console.log(`Generated: ${outputPath}`);
}
// Generate for all your posts
const posts = ['intro-to-rust', 'why-typescript', 'docker-tips'];
for (const post of posts) {
await generateOG(post, `./public/og/${post}.png`);
}
Step 3: Add to Your HTML
<meta property="og:image" content="https://yoursite.com/og/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" />
Why Not Just Use Puppeteer Directly?
You could. But then you need to:
- Maintain a headless Chrome instance
- Handle memory leaks, crashes, and timeouts
- Set up a server to run it
- Deal with fonts, emoji rendering, and platform differences
- Scale it when you have thousands of pages
A screenshot API handles all of that. You send a URL, you get an image back. The free tier (25 screenshots/month) is enough for most blogs.
Advanced: Dynamic Parameters
You can pass query parameters to your template page and generate images on the fly:
/og?title=My+Post&author=Jane&tag=javascript
Then your template reads the URL params:
const params = new URLSearchParams(window.location.search);
document.querySelector('h1').textContent = params.get('title');
Now every page gets a unique, branded OG image without any manual work.
Real-World Results
Sites with proper OG images see 2-3x higher click-through rates on social media shares. It's one of those small details that compounds over time.
Try It
GrabShot has a free tier with 25 screenshots/month: grabshot.dev
The free screenshot tool lets you test it without signing up.
What approach do you use for OG images? I'd love to hear about other solutions in the comments.
Top comments (0)