The ImageResponse API: OG cards as code
Next.js can render an image from JSX at the edge using the ImageResponse API. You write a component describing the card — title, eyebrow, brand mark, accent color — and Next.js turns it into a PNG for that route. It's the same mental model as a page, but the output is an image, so every page can have a tailored preview generated from its own data.
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export default async function OG({ params }) {
const post = postBySlug((await params).slug);
return new ImageResponse(
(<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
width: '100%', height: '100%', padding: 64, background: '#0B0B0F', color: '#fff' }}>
<div style={{ fontSize: 28, color: post.accent }}>{post.tags[0]}</div>
<div style={{ fontSize: 64, fontWeight: 700, lineHeight: 1.1 }}>{post.title}</div>
<div style={{ fontSize: 28, opacity: 0.7 }}>aliasghar.me</div>
</div>), { ...size });
}Per-route, from the page's own data
Because the image reads the same data the page does (title, tag, accent), it stays correct automatically — rename a post and its card updates on the next build. Pair the file with metadata that points at it, and every page ships a unique, accurate preview with zero manual design work per page.
One template + per-page data is the same principle as programmatic SEO: you design the system once, and every page gets a correct, distinct artifact for free. This site generates every OG card this way.
The gotchas worth knowing
- The renderer supports a subset of CSS — use flexbox layout; every element with children needs an explicit display:flex.
- Fonts must be loaded explicitly (fetch the font file and pass it in) — you can't rely on system fonts for consistency.
- Keep the design legible at small sizes; the card is usually seen as a thumbnail, so big type and high contrast win.
- Cache the output — these are static per route, so they generate at build and serve as plain images.
Key takeaways
- Generate OG images from JSX with the ImageResponse API — one template, per-route data.
- Cards read the page's own data, so they stay accurate automatically.
- Mind the constraints: flexbox-only layout, explicit fonts, legible-at-thumbnail design.
- A blank or generic preview wastes every share; automated branded cards recover that click-through.