How to Generate Dynamic OG Images with Next.js and SnapAPI

Feb 10, 2026 · 8 min read

Social media preview images — those rectangular cards that appear when you share a link on Twitter, LinkedIn, or Slack — can make or break your click-through rates. Studies show that posts with rich preview images get 2-3x more engagement than plain text links.

There's a simple approach: design your OG images in HTML/CSS, then screenshot them with SnapAPI. You get pixel-perfect control, any font you want, and images that look exactly like you designed them.

What We're Building

Step 1: Set Up Your SnapAPI API Key

After signing up at snapapi.app, grab your API key. Add it to your .env.local:

SNAPAPI_KEY=your_api_key_here

Step 2: Create the OG Image HTML Template

Design your OG image with standard HTML and CSS. The beauty of this approach is you use tools you already know:

// lib/og-template.ts
export function generateOGTemplate({ title, author, date, category }) {
  return `
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      width: 1200px; height: 630px;
      display: flex; font-family: 'Inter', sans-serif;
      background: linear-gradient(135deg, #0f172a, #1e293b);
      color: white;
    }
    .title { font-size: 56px; font-weight: 800; }
  </style>
</head>
<body>
  <div class="container">
    <span class="category">${category}</span>
    <h1 class="title">${title}</h1>
    <p>${author} · ${date}</p>
  </div>
</body>
</html>`;
}

Step 3: Build the API Route

Create an API route that takes post data, renders the HTML, and calls SnapAPI:

// app/api/og/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get('title') || 'Untitled';
  
  const html = generateOGTemplate({ title, ... });

  const response = await fetch('https://snapapi.app/v1/screenshot', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.SNAPAPI_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html: html,
      width: 1200, height: 630,
      format: 'png',
    }),
  });

  const imageBuffer = Buffer.from(await response.arrayBuffer());
  return new NextResponse(imageBuffer, {
    headers: {
      'Content-Type': 'image/png',
      'Cache-Control': 'public, max-age=86400',
    },
  });
}

Step 4: Add Meta Tags

In your blog post layout, reference the OG image API route:

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPostBySlug(params.slug);
  const ogUrl = `/api/og?title=${encodeURIComponent(post.title)}`;
  
  return {
    openGraph: {
      images: [{ url: ogUrl, width: 1200, height: 630 }],
    },
    twitter: { card: 'summary_large_image', images: [ogUrl] },
  };
}

Why SnapAPI Over @vercel/og?

If your OG images are simple text on a gradient, @vercel/og works fine. But for anything complex — custom layouts, brand assets, rich typography — SnapAPI gives you full HTML/CSS freedom. Any Google Font, any CSS property, any level of complexity.

Cost Breakdown

With SnapAPI's Free tier (100 screenshots/month), you can generate OG images for 100 blog posts. The Starter plan at $19/month covers 1,000 images — more than enough for most blogs. Compare that to running your own Puppeteer instance ($20-40/month) just for OG generation.

Ready to get started?

Sign up for SnapAPI and generate your first OG image in under 5 minutes.

View Plans →