OG Images
Dynamic Open Graph images using next/og ImageResponse. No dependencies — built into Next.js.
Generate branded 1200x630 social cards for every page using next/og's ImageResponse. Renders JSX at the edge — no Figma, no static files.
Setup
1
Create the OG image route
Create app/api/og/route.tsx:
import { ImageResponse } from "next/og"
import type { NextRequest } from "next/server"
export const runtime = "edge"
const SITE_NAME = "YourSaaS"
const SITE_URL = process.env.NEXT_PUBLIC_APP_URL ?? "https://yoursaas.com"
async function loadFont(): Promise<ArrayBuffer> {
const res = await fetch(
"https://fonts.gstatic.com/s/plusjakartasans/v8/LDIbaomQNQcsA88c7O9yZ4KMCoOg4IA6-91aHEjcWuA_KU7NShXUEKi4Rw.woff2"
)
return res.arrayBuffer()
}
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const title = searchParams.get("title") ?? SITE_NAME
const description = searchParams.get("description") ?? "Ship your SaaS faster"
const fontData = await loadFont()
return new ImageResponse(
(
<div style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", justifyContent: "center", padding: "80px", background: "linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 100%)", color: "#ffffff" }}>
<div style={{ fontSize: "20px", color: "rgba(255,255,255,0.6)", marginBottom: "32px", letterSpacing: "2px", textTransform: "uppercase" }}>{SITE_NAME}</div>
<div style={{ fontSize: "64px", fontWeight: 800, lineHeight: 1.15, marginBottom: "24px", color: "#ffffff" }}>{title}</div>
<div style={{ fontSize: "28px", color: "rgba(255,255,255,0.6)", lineHeight: 1.5 }}>{description}</div>
<div style={{ position: "absolute", bottom: "40px", right: "80px", fontSize: "18px", color: "rgba(255,255,255,0.3)" }}>{SITE_URL.replace(/https?:\/\//, "")}</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [{ name: "Plus Jakarta Sans", data: fontData, style: "normal", weight: 800 }],
}
)
}2
Add to page metadata
// Any page.tsx or layout.tsx
import type { Metadata } from "next"
const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "https://yoursaas.com"
export const metadata: Metadata = {
openGraph: {
images: [{
url: `${APP_URL}/api/og?title=${encodeURIComponent("My Page")}&description=${encodeURIComponent("Page description")}`,
width: 1200,
height: 630,
}],
},
twitter: {
card: "summary_large_image",
images: [`${APP_URL}/api/og?title=${encodeURIComponent("My Page")}`],
},
}3
Optional: create a helper
// lib/og.ts
const APP_URL = process.env.NEXT_PUBLIC_APP_URL ?? "https://yoursaas.com"
export function ogImageUrl(title: string, description?: string): string {
const params = new URLSearchParams({ title })
if (description) params.set("description", description)
return `${APP_URL}/api/og?${params.toString()}`
}Environment Variables
NEXT_PUBLIC_APP_URL="https://yourdomain.com"Verification
- Start dev:
pnpm dev - Open
/api/og?title=Hello%20World&description=Testing - A 1200x630 PNG should render in the browser
- Test in Facebook Sharing Debugger or Twitter Card Validator
Notes
ImageResponseruns at the edge — no Node.js required- Results are cached at the CDN edge by default
- No npm dependencies —
next/ogis built into Next.js