Next.js guide
Next.js OG image generator
OGKit is the hosted path for Next.js Open Graph images: absolute HTTPS URLs from generateMetadata, no opengraph-image.tsx, no Satori fonts, no Edge bundle budget.
1) Per-route generateMetadata (primary pattern)
Build the image URL on the server from route data. Pass the same absolute URL to Open Graph and Twitter. Keep OGKIT_KEY in server env only.
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const post = await getPost(params.slug);
const image = new URL("https://www.webmorp.art/api/og/article");
image.searchParams.set("key", process.env.OGKIT_KEY!);
image.searchParams.set("title", post.title);
image.searchParams.set("subtitle", post.excerpt ?? "Blog");
const url = image.toString();
return {
title: post.title,
openGraph: {
title: post.title,
images: [{ url, width: 1200, height: 630, alt: post.title }],
},
twitter: { card: "summary_large_image", title: post.title, images: [url] },
};
}2) Shared helper (DRY across layouts)
Extract a tiny server helper so docs, changelog, and marketing pages share one contract.
// lib/ogkit.ts — server-only
export function ogkitArticle(fields: { title: string; subtitle?: string }) {
const image = new URL("https://www.webmorp.art/api/og/article");
image.searchParams.set("key", process.env.OGKIT_KEY!);
image.searchParams.set("title", fields.title);
if (fields.subtitle) image.searchParams.set("subtitle", fields.subtitle);
return image.toString();
}
// usage in generateMetadata
const url = ogkitArticle({ title: post.title, subtitle: post.excerpt });3) Static marketing page (build-time URL)
For a single landing page you can use a module-scope URL. Still keep the key server-side — this file must not be imported from a Client Component.
// app/pricing/page.tsx
const ogImage = new URL("https://www.webmorp.art/api/og/brand");
ogImage.searchParams.set("key", process.env.OGKIT_KEY!);
ogImage.searchParams.set("title", "Acme Pricing");
ogImage.searchParams.set("tagline", "Simple plans");
export const metadata = {
openGraph: { images: [{ url: ogImage.toString(), width: 1200, height: 630 }] },
twitter: { card: "summary_large_image" as const, images: [ogImage.toString()] },
};Typical Next.js mistakes
- Relative og:image paths (scrapers resolve against the wrong host)
- Putting OGKIT_KEY in NEXT_PUBLIC_ or client bundles
- Static metadata export that never updates per slug
- Shipping opengraph-image.tsx and OGKit URL for the same route (last write wins / confusion)
- Expecting Slack/Facebook to drop cache without a new image URL
Wrong — client / public env
// ❌ leaks key + crawlers may miss tags
"use client";
const url = `${process.env.NEXT_PUBLIC_OGKIT}/api/og/...`;Right — server metadata
// ✅ generateMetadata / Server Component only
image.searchParams.set("key", process.env.OGKIT_KEY!);Rescrape after deploys
Next.js metadata changes only help after HTML is live and platforms refetch og:image. Deterministic OGKit URLs cache hard — change title/subtitle (or add v=) when the card must update.
- Deploy and curl the page — confirm absolute og:image / twitter:image match.
curl -sIthe image URL — expectimage/png.- Facebook Sharing Debugger → Scrape Again; LinkedIn Post Inspector.
- Slack: reshare with a new image URL if the old unfurl sticks.
Full matrix: Caching & rescrape. Symptom → fix: Troubleshooting.
Why not always opengraph-image.tsx?
Custom ImageResponse routes win for pixel-perfect JSX. They also make you own fonts, Edge budgets, and render failures. OGKit wins when the job is a reliable 1200×630 card from page fields. Compare: OGKit vs @vercel/og.
Production checklist
- Build URLs in generateMetadata / Server Components only.
- Keep OGKIT_KEY out of client components and NEXT_PUBLIC_*.
- Use absolute HTTPS URLs with width/height when possible.
- Mirror the same URL in openGraph.images and twitter.images.
- After title changes, update the OGKit query (or v=) and rescrape.
Related Next.js SEO pages
Frequently asked questions
How do I add dynamic Open Graph images to Next.js App Router?
Build an absolute OGKit image URL inside generateMetadata (or a server helper), then assign it to metadata.openGraph.images and twitter.images. Use params/slug data so each route gets a unique card.
Can OGKit replace a custom opengraph-image.tsx route?
Yes, when you want hosted templates and stable image URLs instead of maintaining a custom Satori route, font loading, and renderer debugging. Prefer one approach per route — do not mix both.
Where should I keep the OGKit API key?
Keep the API key in server-side environment variables (OGKIT_KEY). Never NEXT_PUBLIC_OGKIT_KEY or client components.
Why is Slack/Facebook still showing the old Next.js card?
Unfurl caches stick to the image URL. Change the OGKit query (title/subtitle or v=), redeploy metadata, then rescrape. See /guides/troubleshooting and /guides/caching-and-rescrape.
Related reading
- Troubleshooting OG images — blank cards, wrong titles, key errors, sticky Slack cache.
- OGKit vs @vercel/og — when a hosted URL API beats maintaining ImageResponse.
- Open Graph images for SEO and social.
OGKit turns one HTTPS URL into a 1200×630 Open Graph image. Read the API reference, deep guides, the Open Graph SEO guide, try the Playground, or sign in to create API keys.