Don't like ads? Go Ad-Free Today

Automated OG Image Generation Build Dynamic Social Cards with a Screenshot API

Published on

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.

Automated OG Image Generation: Build Dynamic Social Cards with a Screenshot API
ADVERTISEMENT · REMOVE?

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:

  1. Create an HTML template for your OG card
  2. Host it with dynamic parameters (title, author, etc.)
  3. Point your og:image meta tag at the screenshot API
  4. 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. 📈

Want To enjoy an ad-free experience? Go Ad-Free Today

Install Our Extensions

Add IO tools to your favorite browser for instant access and faster searching

Add to Chrome Extension Add to Edge Extension Add to Firefox Extension Add to Opera Extension

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!

ADVERTISEMENT · REMOVE?
ADVERTISEMENT · REMOVE?
ADVERTISEMENT · REMOVE?

News Corner w/ Tech Highlights

Get Involved

Help us continue providing valuable free tools

Buy me a coffee
ADVERTISEMENT · REMOVE?