Generate Dynamic OG Images in Next.js
Generate dynamic Open Graph images in Next.js using the ImageResponse API with custom fonts, gradients, and data-driven content for social sharing.
Tags
Generate Dynamic OG Images in Next.js
TL;DR
Use the Next.js ImageResponse API in opengraph-image.tsx route files to generate dynamic, data-driven social preview images at the edge using JSX and inline styles. No Puppeteer, no Canvas, no external services needed.
The Problem
Static OG images are a pain to maintain. Every blog post, product page, or user profile needs a unique social preview image. Designing them manually in Figma and exporting PNGs does not scale. And generic fallback images get ignored in social feeds because they all look the same.
You need images generated dynamically from your data — the blog title, author name, publish date — rendered as a visually appealing card image on the fly.
The Solution
Next.js provides the ImageResponse API from next/og that lets you write JSX to define your OG image layout, and it renders it as a PNG at the edge.
Create an opengraph-image.tsx file in your route folder:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { getBlogPost } from '@/lib/blog';
export const runtime = 'edge';
export const alt = 'Blog post preview';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getBlogPost(params.slug);
// Load a custom font
const interBold = fetch(
new URL('../../assets/Inter-Bold.ttf', import.meta.url)
).then((res) => res.arrayBuffer());
return new ImageResponse(
(
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
width: '100%',
height: '100%',
padding: '60px',
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
color: '#f8fafc',
fontFamily: 'Inter',
}}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ fontSize: 24, color: '#38bdf8', textTransform: 'uppercase' }}>
{post.category}
</div>
<div style={{ fontSize: 52, fontWeight: 700, lineHeight: 1.2 }}>
{post.title}
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', fontSize: 24 }}>
<span>{post.author}</span>
<span style={{ color: '#64748b' }}>|</span>
<span style={{ color: '#94a3b8' }}>{post.readTime}</span>
</div>
</div>
),
{
...size,
fonts: [
{ name: 'Inter', data: await interBold, style: 'normal', weight: 700 },
],
}
);
}For caching and revalidation, add route segment config:
// Control how often the image regenerates
export const revalidate = 86400; // Regenerate once per day
// Or generate at build time for static content
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}Next.js automatically sets the og:image meta tag when it detects an opengraph-image.tsx file in the route. No need to manually add meta tags in your layout or page.
Why This Works
The ImageResponse API uses Satori under the hood, which converts JSX and CSS flexbox layouts into SVG, then rasterizes to PNG. It runs at the edge with minimal cold-start time, so images generate quickly even on first request. The JSX templating approach means you can use any data from your CMS, database, or file system to populate the image — title, author, category, tags, or even dynamic stats. And because it is just a route file, it benefits from Next.js caching, ISR, and static generation out of the box.
FAQ
How do I create dynamic OG images in Next.js?
Create an opengraph-image.tsx file in your route folder and export a default function that returns a new ImageResponse with JSX-based image content.
Can I use custom fonts in OG images?
Yes, fetch font files in your OG image route and pass them to the ImageResponse fonts option to render text with custom typography in generated images.
Are dynamic OG images generated on every request?
They can be cached using route segment config. Set revalidate to control how often images regenerate, or use static params for build-time generation.
Collaboration
Need help with a project?
Let's Build It
I help startups and established companies design, build, and scale world-class digital products. From deep technical architecture to pixel-perfect UI — let's bring your vision to life.
Related Articles
TypeScript Utility Types You Should Know
Five essential built-in generic utility types in TypeScript that will save you hundreds of lines of code.
GitHub Actions Reusable Workflows: Stop Repeating Yourself
Create reusable GitHub Actions workflows with inputs, secrets, and outputs to eliminate YAML duplication across repositories and teams efficiently.
Node.js Streams for Processing Large Files
Process large files efficiently in Node.js using readable, writable, and transform streams to avoid memory issues and handle data chunk by chunk.