MackHow to auto-generate OG images for every blog post using HTML templates and an API. Works with any framework.
Every time you share a link on Twitter, Slack, Discord, or LinkedIn, the platform fetches your og:image meta tag and displays a preview. If you don't have one, your link looks like this:
Your Blog Post Title
yourdomain.com
Boring. Forgettable. Zero clicks.
But if you generate a custom OG image for every post:
🖼️ A beautiful card with your title, author name, date, and brand colors
Way more clicks. Here's how to automate it.
og:image meta tag to the generated image URLNo Figma. No Canva. No manual work per post.
OG images should be 1200×630 pixels. Here's a starter template:
<div style="
width: 1200px; height: 630px;
display: flex; flex-direction: column;
justify-content: center; padding: 60px 80px;
background: linear-gradient(135deg, #0f0f13 0%, #1a1a2e 100%);
font-family: 'Inter', sans-serif; color: white;
">
<div style="font-size: 48px; font-weight: 800; line-height: 1.2; margin-bottom: 24px;">
{{title}}
</div>
<div style="font-size: 20px; color: #888; margin-bottom: auto;">
{{author}} · {{date}} · {{read_time}} read
</div>
<div style="font-size: 24px; font-weight: 700; color: #00d68f;">
yourblog.com
</div>
</div>
Use an HTML-to-image API to convert this template into a PNG:
curl -X POST https://rendly-api.fly.dev/api/v1/images \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<div style=\"width:1200px;height:630px;display:flex;flex-direction:column;justify-content:center;padding:60px 80px;background:linear-gradient(135deg,#0f0f13,#1a1a2e);font-family:Inter,sans-serif;color:white;\"><div style=\"font-size:48px;font-weight:800;line-height:1.2;margin-bottom:24px;\">How I Built a SaaS in a Weekend</div><div style=\"font-size:20px;color:#888;margin-bottom:auto;\">Mack · Feb 15, 2026 · 5 min read</div><div style=\"font-size:24px;font-weight:700;color:#00d68f;\">rendly-api.fly.dev</div></div>",
"viewport": {"width": 1200, "height": 630},
"format": "png"
}'
In your HTML <head>:
<meta property="og:image" content="https://rendly-api.fly.dev/api/v1/renders/YOUR_RENDER_ID/image" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
Generate at build time in getStaticProps:
export async function getStaticProps({ params }) {
const post = getPostBySlug(params.slug);
const res = await fetch('https://rendly-api.fly.dev/api/v1/images', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.RENDLY_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
html: ogTemplate(post.title, post.author, post.date),
viewport: { width: 1200, height: 630 }
})
});
const { image_url } = await res.json();
return { props: { post, ogImage: image_url } };
}
Generate in your controller or as a helper:
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
@og_image = generate_og_image(@post)
end
private
def generate_og_image(post)
conn = Faraday.new(url: 'https://rendly-api.fly.dev')
res = conn.post('/api/v1/images') do |req|
req.headers['Authorization'] = "Bearer #{ENV['RENDLY_API_KEY']}"
req.headers['Content-Type'] = 'application/json'
req.body = {
html: render_to_string(partial: 'posts/og_template', locals: { post: post }),
viewport: { width: 1200, height: 630 }
}.to_json
end
JSON.parse(res.body)['image_url']
end
end
google_fonts: ["Inter", "Fira Code"] to load them.If you don't want to code anything, use Rendly's built-in templates:
curl -X POST https://rendly-api.fly.dev/api/v1/templates/og-blog-post/render \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"variables": {
"title": "My Amazing Blog Post",
"author": "Your Name",
"date": "Feb 15, 2026"
}
}'
10 built-in templates, zero design work.
Rendly is a free screenshot and OG image API. 100 renders/month free, no credit card required.