Back to Blog

Build a Dynamic Social Preview (OG Image) Service with the Screenshots API

Open Graph (OG) and Twitter Card images boost CTR and social sharing. Instead of running your own headless renderer, point Supacrawler’s Screenshots API at a hosted HTML template and generate images on demand.

Architecture

  1. A hosted HTML/CSS template (e.g., /og-template) that reads query params (title, author, theme)
  2. An API route that calls the Screenshots API to capture the template URL
  3. A cache layer (filesystem, KV, or object storage)

1) Minimal HTML template

Host a responsive template in your app (Next.js, static site, etc.).

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
body { margin: 0; font-family: Inter, system-ui; background: #0b0f19; color: #fff; }
.wrap { display: flex; flex-direction: column; justify-content: center; height: 100vh; padding: 64px; }
h1 { font-size: 72px; line-height: 1.05; margin: 0 0 16px 0; }
p { font-size: 28px; opacity: 0.9; margin: 0; }
</style>
</head>
<body>
<div class="wrap">
<h1 id="title"></h1>
<p id="meta"></p>
</div>
<script>
const q = new URLSearchParams(location.search)
document.getElementById('title').textContent = q.get('title') || 'Hello, OG!'
document.getElementById('meta').textContent = q.get('meta') || 'By Supacrawler'
document.body.style.background = q.get('bg') || '#0b0f19'
</script>
</body>
</html>

2) Next.js API route to generate OG images

Render the template at a fixed viewport and return a binary image.

// app/api/og/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { SupacrawlerClient, ScreenshotCreateRequest } from '@supacrawler/js'
const client = new SupacrawlerClient({ apiKey: process.env.SUPACRAWLER_API_KEY! })
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const title = searchParams.get('title') || 'Untitled'
const meta = searchParams.get('meta') || 'By Supacrawler'
const bg = searchParams.get('bg') || '#0b0f19'
const templateUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/og-template?title=${encodeURIComponent(title)}&meta=${encodeURIComponent(meta)}&bg=${encodeURIComponent(bg)}`
const job = await client.createScreenshotJob({
url: templateUrl,
device: ScreenshotCreateRequest.device.CUSTOM,
width: 1200,
height: 630,
format: ScreenshotCreateRequest.format.PNG,
wait_until: 'domcontentloaded',
block_ads: true,
})
const res = await client.waitForScreenshot(job.job_id!)
const img = await fetch(res.screenshot).then(r => r.arrayBuffer())
return new NextResponse(Buffer.from(img), { headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=31536000, immutable' } })
}

3) Caching and storage

  • Derive a cache key from the query params (e.g., SHA-1 of title|meta|bg)
  • Store renders in object storage (S3, R2) and respond with a redirect when hit
  • For static content, pre‑render popular combinations at build time

Rendering tips

  • Use CUSTOM 1200x630 to match OG dimensions
  • domcontentloaded is enough for static templates; add a small delay if using web fonts
  • Prefer PNG for text‑heavy cards; WebP if file size matters

That’s it—dynamic social images with clean templates, stable rendering, and no headless infra. Wire the route into your <meta property="og:image" /> and ship.

By Supacrawler Team
Published on September 6, 2025