Next.js Open Graph Meta Tags: The Complete Setup Guide
Add og:title, og:image, og:description and Twitter card tags to your Next.js app using the Metadata API, generateMetadata, and dynamic OG images.
The right way to set OG tags in Next.js 13+
Next.js 13 introduced the Metadata API — a typed, colocated way to define Open Graph tags per route. Instead of manually adding <meta> tags to a custom _document.tsx, you export a metadata object (or generateMetadata function) from each page.tsx or layout.tsx.
Static metadata export
For pages with fixed OG data, export a metadata const from your page file:
// app/page.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Your Page Title",
description: "Your page description here.",
openGraph: {
title: "Your Page Title",
description: "Your page description here.",
url: "https://yourdomain.com",
siteName: "Your Site",
images: [
{
url: "https://yourdomain.com/og.jpg",
width: 1200,
height: 630,
alt: "Your OG image alt text",
},
],
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Your Page Title",
description: "Your page description here.",
images: ["https://yourdomain.com/og.jpg"],
},
};Dynamic metadata with generateMetadata
For dynamic routes (blog posts, product pages), use generateMetadata to pull data per route:
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
interface Props {
params: { slug: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await fetchPost(params.slug); // your data fetching
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
url: `https://yourdomain.com/blog/${params.slug}`,
type: "article",
images: [
{
url: post.ogImage ?? "https://yourdomain.com/og-default.jpg",
width: 1200,
height: 630,
},
],
},
twitter: {
card: "summary_large_image",
images: [post.ogImage ?? "https://yourdomain.com/og-default.jpg"],
},
};
}Dynamic OG images with next/og
Next.js ships a built-in ImageResponse API (via next/og) that generates OG images at the edge from JSX. Create an image route:
// app/og/route.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const title = searchParams.get("title") ?? "Default Title";
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#000",
color: "#fff",
fontSize: 60,
fontWeight: "bold",
}}
>
{title}
</div>
),
{ width: 1200, height: 630 }
);
}
// Then reference it in metadata:
// images: [{ url: `/og?title=${encodeURIComponent(post.title)}` }]Common mistakes in Next.js OG setup
- Relative image URLs:
images: [{ url: '/og.jpg' }]does not work — always use absolute URLs including the domain. - Missing width/height in the images array: Next.js renders the meta tags but crawlers still benefit from explicit dimensions. Include
width: 1200andheight: 630. - metadata export in a Client Component:
export const metadataonly works in Server Components. Mark client files with'use client'and move metadata to a parent server layout or page. - og:url not set: always include
urlin theopenGraphobject so crawlers know the canonical URL for cache keying. - Caching on deploy: social platforms cache OG data. After redeploying, use platform debugger tools to bust the cache.
Verify your Next.js OG tags
After deploying, view source on your live page and search for og:image. If you see it, you're set. If not, check that your route is a Server Component and that the absolute URL resolves publicly.
Want to verify your Next.js OG tags are rendering correctly? Paste your production URL into OG Fixer to see the live preview across Twitter, LinkedIn, Discord, and more.