The pattern
- Build an HTML template at
https://yourdomain.com/og/<slug> that renders a 1200×630 OG card for any post.
- Hit
aiSnapApi with that URL and width=1200&height=630.
- Cache the result at your CDN with a long
Cache-Control.
- Set
<meta property="og:image"> on your post page to your cached URL.
One screenshot per unique post. Twitter, LinkedIn, Slack, Discord all hit your CDN — not the API.
Next.js App Router: /api/og
// app/api/og/route.ts
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const slug = searchParams.get('slug');
const target = `https://yourdomain.com/og-template/${slug}`;
const r = await fetch(
`https://api.aisnapapi.com/v1/screenshot?url=${encodeURIComponent(target)}&width=1200&height=630`,
{ headers: { Authorization: `Bearer ${process.env.SNAPAPI_KEY!}` } }
);
return new Response(r.body, {
headers: {
'Content-Type': 'image/png',
// Cache at the CDN for 1 year — change the slug to bust
'Cache-Control': 'public, max-age=31536000, immutable'
}
});
}
Then in your post page:
<meta property="og:image" content="https://yourdomain.com/api/og?slug=my-post" />
<meta name="twitter:card" content="summary_large_image" />
Why screenshot-based OG, not @vercel/og?
- Real CSS, web fonts, gradients, and animations render exactly as they do in a browser.
@vercel/og uses Satori, which supports a Tailwind subset — sometimes too narrow.
- Reuse your existing components. The OG template is just another page on your site, written in your normal stack (React, Vue, Astro, plain HTML).
- Test in a browser. Open the OG template URL directly to debug — no separate rendering environment.
- No edge-runtime constraints. Use any library, any node module.
Common pitfalls
- Cache, cache, cache. Without CDN caching, every share regenerates the image. With caching, you burn 1 quota per unique post — for life.
- Wait for web fonts. Add
&delay=500 if you see fallback fonts in the rendered image.
- Twitter previews update slowly. Test with Twitter's Card Validator to force a refresh.
- Pin dimensions. Always pass
width=1200&height=630 so Twitter / Facebook / LinkedIn render at the right aspect ratio.