Next.js is one of the most popular frameworks for building modern web apps, and it has excellent built-in support for Open Graph images — both static and dynamically generated. Here's how to set them up in the App Router, with the exact code for each approach.
Method 1: Static OG Image (Simplest)
If your pages share the same design or you generate images ahead of time, export a metadata object from your page or layout:
import type { Metadata } from 'next'
export const metadata: Metadata = {
openGraph: {
title: 'My Page',
description: 'Page description',
images: [{
url: 'https://yoursite.com/og-image.png',
width: 1200,
height: 630,
}],
},
}
This works for static pages, blog posts with known content, and marketing pages. The image is referenced by URL, so it must live somewhere reachable — the /public directory is the natural home. Include width and height: Next.js emits them as og:image:width / og:image:height, which keeps Facebook from re-scanning the file before rendering.
Method 2: Dynamic OG Images with Satori
Next.js App Router supports generating OG images on-the-fly using Satori (JSX to SVG) and resvg (SVG to PNG) — this is how you get a unique card per page with the title baked in, no design tool needed. Create a file at app/og/route.tsx:
import { ImageResponse } from 'next/og'
export async function GET() {
return new ImageResponse(
(
<div style={{
width: 1200, height: 630,
display: 'flex', alignItems: 'center',
justifyContent: 'center', fontSize: 64,
background: '#4f46e5', color: 'white',
}}>
Hello from OG Image
</div>
),
{ width: 1200, height: 630 }
)
}
To make it per-page, read the path or query params inside GET() and pass the title into the JSX. Two practical notes from running this in production:
- Fonts must be loaded explicitly — Satori has no access to system fonts.
fetch()a font file (Google Fonts works) and pass it viafontsin the options, or text will render with fallbacks that look off. - Cache the response — add
Cache-Control: public, max-age=31536000, immutableon the route; the image for a given title never changes, and without caching every share triggers a fresh render on the serverless runtime.
Method 3: Use Our Generator
For the fastest results — especially if you don't want to touch code or your images need to match a brand kit — use our free OG image generator. Design your image visually, download the PNG, and reference it in your metadata. No code required. It's the right call when the design matters more than the pipeline, and it's what most marketing teams end up using even on Next.js sites.
Testing Your Setup
After configuring your OG images, use our OG Checker tool to verify your tags are correct before sharing. Then do a real-world check: paste the URL into Discord or Slack (neither has a public debugger) and confirm the unfurl shows your image. If it doesn't, check that your CDN doesn't block crawler user-agents — a common failure that looks like a Next.js bug but isn't.
Key Takeaways
- Use
export const metadatafor static OG images - Use
app/og/route.tsxfor dynamic generation with Satori - Always include og:image:width and og:image:height
- Load fonts explicitly and cache dynamic OG routes
- Test with our free OG checker before deploying