OG Image Templates: Design Consistent Social Preview Cards at Scale
How to design and build OG image templates for consistent social preview cards across every page of your site — using Figma, Satori, @vercel/og, or Cloudinary.
Updated March 2026
What Makes a Good OG Image Template?
A good OG image template is a reusable design that generates a unique, branded image for every page — with the page title or key content swapped in automatically. The goals:
- Recognizable: Your brand colors, logo, or visual style should be immediately identifiable.
- Readable at a glance: Large text, high contrast — these images are viewed at 1200×630 compressed down to ~400px wide in the feed.
- Dynamic by default: Page title/description should change per-page, not be static text baked in.
- 1200×630px: The universal OG standard size (1.91:1 ratio).
Template Design Best Practices
Keep Text Large (min 48px)
When rendered at actual social card size (typically 400–550px wide on desktop), your 60px text at 1200px wide becomes 20–28px. Text below 48px in your template risks being unreadable.
Use High Contrast
White text on dark background, or dark text on light background. Avoid mid-gray — it renders poorly in compressed JPEG form.
Leave Space for Long Titles
Blog post titles can be 80–100 characters. Your template needs to handle them without overflow. Either:
- Truncate at 60 characters with ellipsis
- Use word wrap with 2–3 line limit
- Dynamically resize font based on title length
Include Your Domain or Logo
Twitter and LinkedIn show the source domain below the card — but including it in the image itself reinforces brand recognition even on platforms that don't show the URL prominently (Discord, WhatsApp).
Approach 1: @vercel/og with JSX Template
The cleanest approach for Next.js sites:
// app/og/route.tsx — branded template
import { ImageResponse } from "next/og";
export const runtime = "edge";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get("title") ?? "My Site";
const category = searchParams.get("category") ?? "Blog";
return new ImageResponse(
(
<div
style={{
width: 1200,
height: 630,
display: "flex",
flexDirection: "column",
background: "#0a0a0a",
padding: "60px 80px",
position: "relative",
}}
>
{/* Background accent */}
<div style={{
position: "absolute",
top: 0,
right: 0,
width: 400,
height: 400,
background: "radial-gradient(circle, rgba(139,92,246,0.3) 0%, transparent 70%)",
display: "flex",
}} />
{/* Category badge */}
<div style={{
display: "flex",
alignItems: "center",
background: "rgba(139,92,246,0.2)",
border: "1px solid rgba(139,92,246,0.5)",
borderRadius: 8,
padding: "6px 16px",
width: "fit-content",
marginBottom: 40,
}}>
<span style={{ color: "#a78bfa", fontSize: 20, fontWeight: 600 }}>{category}</span>
</div>
{/* Title */}
<div style={{
color: "white",
fontSize: title.length > 50 ? 52 : 64,
fontWeight: 800,
lineHeight: 1.1,
maxWidth: 900,
flex: 1,
display: "flex",
alignItems: "center",
}}>
{title}
</div>
{/* Footer: brand */}
<div style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginTop: 40,
paddingTop: 24,
borderTop: "1px solid rgba(255,255,255,0.1)",
}}>
<span style={{ color: "#9ca3af", fontSize: 24 }}>ogfixer.com</span>
<span style={{ color: "#9ca3af", fontSize: 20 }}>Social Preview Testing Tool</span>
</div>
</div>
),
{ width: 1200, height: 630 }
);
}Approach 2: Figma Template + Manual Export
For teams without server-side image generation, Figma + manual export is still viable:
- Create a Figma frame at 1200×630px
- Design your template with placeholder text layers
- Duplicate the frame for each page, update the title text
- Export all frames as PNG at 1x (already at 1200×630)
- Upload to your CDN/hosting (GCS, S3, Cloudinary, or your server)
- Reference each URL in the page's og:image tag
This is the lowest-tech approach and works perfectly well for sites with <50 pages.
Approach 3: Cloudinary Text Overlay
If you're already using Cloudinary, use URL-based text overlays on a background template:
// Generate OG URL from title
function getOgUrl(title: string): string {
const encoded = encodeURIComponent(title.substring(0, 60));
return [
"https://res.cloudinary.com/YOUR_CLOUD/image/upload",
"c_fill,w_1200,h_630",
`l_text:Arial_60_bold:${encoded},co_white,g_west,x_80,w_900,c_fit`,
"og-template.jpg",
].join("/");
}Multiple Template Types
Consider having different templates for different content types:
- Blog articles — title + category + author name
- Product/landing pages — headline + CTA or benefit
- Docs pages — page title + section breadcrumb
- Homepage — tagline + brand hero image
Test your OG image template →
OGFixer renders your og:image exactly as it appears on Twitter, LinkedIn, Discord, and Slack — perfect for checking your template looks right before launch.
Preview my OG image template →