How to Generate Dynamic OG Images in Next.js
Open Graph images are the thumbnail that appears when someone shares your link on Twitter, LinkedIn, or Slack. A great OG image can significantly increase click-through rates. This tutorial shows you how to generate dynamic, per-page OG images in Next.js using the SnapSharp API.
The problem with static OG images
Most developers set a single og:image for their entire site. It works, but it's a missed opportunity. Blog posts, product pages, and user profiles all deserve their own unique social cards that reflect their content.
The traditional alternative — generating images server-side with Canvas or Puppeteer — is complex, expensive to maintain, and slow.
A better approach: API-based OG images
SnapSharp handles the rendering. You pass data, get back an image.
Setting up
Install the package (optional — you can also use plain fetch):
pnpm add @snapsharp/sdkOr just use fetch directly — the API is simple enough.
Generating images in Next.js App Router
Create an OG image route at app/og/route.ts:
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const title = req.nextUrl.searchParams.get('title') ?? 'My Blog';
const author = req.nextUrl.searchParams.get('author') ?? 'Anonymous';
const res = await fetch('https://api.snapsharp.dev/v1/og-image', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SNAPSHARP_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'blog-post',
data: { title, author, date: new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }) },
format: 'jpeg',
}),
next: { revalidate: 3600 },
});
const buffer = await res.arrayBuffer();
return new NextResponse(buffer, {
headers: { 'Content-Type': 'image/jpeg', 'Cache-Control': 'public, max-age=3600' },
});
}Adding metadata to your pages
In your blog post page (app/blog/[slug]/page.tsx):
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = getPost(params.slug);
const ogUrl = `https://your-site.com/og?title=${encodeURIComponent(post.title)}&author=${encodeURIComponent(post.author)}`;
return {
title: post.title,
openGraph: {
images: [{ url: ogUrl, width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
images: [ogUrl],
},
};
}Result
Every blog post now gets a unique, beautifully designed social card. When shared on Twitter or LinkedIn, it shows the post title, author, and publication date — all rendered automatically.
Performance tips
- Use
next: { revalidate: 3600 }to cache API responses at the CDN level - Add
Cache-Control: public, max-age=86400to cache OG images aggressively - Use
format: 'jpeg'for 30–50% smaller file sizes vs PNG
The entire setup takes about 10 minutes and works for any content type — blog posts, product pages, user profiles, and more.