Open Graph tags in Next.js (App Router)
Next.js has a built-in Metadata API since the App Router — no more writing loose head tags, just export an object. It is the cleanest setup of any framework, as long as you know two pitfalls: metadataBase and the difference between static and dynamic metadata.
The free checker shows your previews for nine platforms — from X and WhatsApp to iMessage, Discord and Bluesky.
Static: export metadata
In any layout.js or page.js (server component!) you export a metadata object with openGraph and twitter fields. Next automatically renders the right meta tags from it. See the example below.
Set metadataBase in your root layout
With metadataBase (e.g. new URL("https://yoursite.com")) relative URLs in openGraph.url and images are automatically made absolute. Forget it, and your og:image URLs stay relative — and scrapers ignore them.
Dynamic: generateMetadata
For pages with dynamic data (products, blog posts) export an async function generateMetadata({ params }) that returns the object based on your data. Note: in recent Next versions params is a Promise — await it first.
OG images via file conventions
Drop an opengraph-image.png (1200×630) next to your page.js and Next wires it up automatically. Want generated images (the title rendered into the image), use ImageResponse from next/og in an opengraph-image.js.
Code example
// app/layout.js — site-wide defaults
export const metadata = {
metadataBase: new URL('https://yoursite.com'),
title: 'Your site name',
description: 'What your site does in one sentence.',
openGraph: {
title: 'Your site name',
description: 'What your site does in one sentence.',
type: 'website',
url: '/',
siteName: 'Your site name',
},
twitter: { card: 'summary_large_image' },
};
// app/blog/[slug]/page.js — dynamic per post
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
url: `/blog/${slug}`,
images: [{ url: post.ogImage, width: 1200, height: 630 }],
},
};
}Common mistakes
- Exporting metadata from a client componentA file with "use client" cannot export metadata. Keep the page a server component and move interactivity into a child component.
- Forgetting metadataBaseWithout metadataBase, relative image URLs stay relative — and scrapers ignore those. Set it once in your root layout.
- The Pages Router works differentlyStill on the Pages Router? Then you work with <Head> from next/head and write the meta tags yourself. The Metadata API is App Router-only.
Frequently asked questions
How do I make dynamic OG images (title rendered in the image)?
With ImageResponse from next/og in an opengraph-image.js file convention: you write JSX that is rendered to a PNG the moment a scraper requests the image.
Are layout and page metadata merged?
Yes — page metadata overrides layout metadata per field. Defaults in the layout, specifics per page.
Do my tags work with client-side navigation?
For scrapers only the server response matters, and with Next that is always correct — scrapers make a fresh request per URL.
Paste your URL and see within two seconds whether everything is right — on every platform.