How to generate dynamic OG images with a screenshot API

How to generate dynamic OG images with a screenshot API

Automate social cards that actually get clicks.

March 28, 2026 · 8 min read

Open Graph images are the thumbnails that appear when someone shares your URL on Twitter, LinkedIn, Slack, or iMessage. A good OG image increases click-through rate by 2–3x compared to no image or a generic logo. But creating them manually for every page doesn't scale.

The standard approach is Vercel's @vercel/og library, which renders React components to images at the edge. It's fast and works well for simple text-on-gradient designs. But if you need your OG image to look like the actual page — or if you're not on Vercel — a screenshot API is a simpler and more flexible alternative.

The approach: template HTML + screenshot

The idea is simple: create an HTML template that renders your OG image, then screenshot it at 1200×630 (the standard OG dimension). The template can be as complex as you want — full CSS, custom fonts, images, gradients — because it's rendered in a real browser.

Step 1: Create a template page

Build a page that accepts parameters via query string and renders a card:

og-template.html (hosted at your domain)
<!DOCTYPE html>
<html>
<body style="margin:0;width:1200px;height:630px;display:flex;align-items:center;
  justify-content:center;background:linear-gradient(135deg,#0a0a1a,#1a0a2e);
  font-family:system-ui;color:white;padding:80px;">
  <div>
    <h1 id="title" style="font-size:56px;line-height:1.2;margin:0 0 20px;"></h1>
    <p id="subtitle" style="font-size:24px;opacity:0.7;"></p>
  </div>
  <script>
    const params = new URLSearchParams(location.search);
    document.getElementById('title').textContent = params.get('title') || 'Default Title';
    document.getElementById('subtitle').textContent = params.get('subtitle') || '';
  </script>
</body>
</html>

Step 2: Screenshot the template

Node.js
async function generateOGImage(title, subtitle) {
  const templateUrl = `https://yoursite.com/og-template?title=${
    encodeURIComponent(title)
  }&subtitle=${encodeURIComponent(subtitle)}`;

  const response = await fetch(
    'https://api.nightglass.xyz/api/v1/screenshot',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_API_KEY',
      },
      body: JSON.stringify({
        url: templateUrl,
        width: 1200,
        height: 630,
        deviceScaleFactor: 1, // OG images don't need retina
      }),
    }
  );

  return Buffer.from(await response.arrayBuffer());
}

Step 3: Serve and cache

Generate the image on first request and cache it aggressively. OG images don't change often — a Cache-Control of 1 week is reasonable. Store in S3/R2/your CDN.

// In your Next.js API route or Express handler:
app.get('/api/og/:slug', async (req, res) => {
  const cached = await cache.get(`og:${req.params.slug}`);
  if (cached) return res.type('png').send(cached);

  const post = await getPost(req.params.slug);
  const image = await generateOGImage(post.title, post.excerpt);

  await cache.set(`og:${req.params.slug}`, image, { ex: 604800 });
  res.type('png').send(image);
});

Why screenshot API vs Vercel OG?

Vercel OG is great if you're on Vercel and your designs are simple (text, basic shapes, solid colours). It uses Satori to convert React to SVG, which limits CSS support — no grid, limited flexbox, no box-shadow, no gradients in text.

Screenshot API renders in a real browser, so you get full CSS support. Complex layouts, web fonts, background images, blur effects — anything a browser can render. The trade-off is latency (~2s vs ~200ms), but since you're caching the result, this only matters on the first request.

The meta tag

Point your OG image tag at the generation endpoint:

<meta property="og:image" content="https://yoursite.com/api/og/my-blog-post">

When a social platform crawls your page, it fetches this URL and gets a dynamically generated image tailored to that specific page.

Pro tip: Test your OG images with the Twitter Card Validator and Facebook's Sharing Debugger. Both show you exactly what the card will look like before you share.

If also building link previews (thumbnails when users paste URLs), the same screenshot approach applies — see the link preview guide. For more HTML-to-image options, see HTML to image: 4 ways to convert HTML to PNG. Comparing API costs? The pricing comparison breaks down PAYG vs subscription.