The Next.js Metadata API — Practical Guide
What is the Next.js Metadata API?
Next.js 13 introduced a native Metadata API for the App Router. Instead of manually placing <meta> tags in your HTML, you export a metadata object — or a generateMetadata function — from any layout.js or page.js file. Next.js handles the rest, injecting the right tags into your <head> automatically.
This approach is type-safe, co-located with your page logic, and supports both static and dynamic values. It replaces the older pattern of using next/head in the Pages Router.
app/ directory. If you are still on the Pages Router, you need next/head instead.Skip the docs. Generate your metadata object visually.
Open the Builder →Basic fields — title, description, keywords
The most common fields you will set are title and description. These map directly to the <title> tag and <meta name="description"> tag in your HTML output.
export const metadata = {
title: 'My App',
description: 'The best app on the internet.',
keywords: ['nextjs', 'react', 'web app'],
};Title templates
If you define metadata in a root layout.js, you can use a title template so that child pages automatically get a branded suffix — without any extra code in those pages.
// app/layout.js
export const metadata = {
title: {
template: '%s | My Brand',
default: 'My Brand',
},
};
// app/blog/page.js
export const metadata = {
title: 'Blog', // Renders as "Blog | My Brand"
};The %s placeholder is replaced by the child page's title. The default value is used when no child title is set — for example, on the homepage.
Authors and creator
You can declare content authorship using the authors, creator, and publisher fields. These are optional but useful for news, blog, and documentation sites.
export const metadata = {
authors: [{ name: 'Jane Doe', url: 'https://janedoe.com' }],
creator: 'Jane Doe',
publisher: 'Acme Publishing',
};Open Graph — Social sharing previews
Open Graph (OG) tags control how your page looks when shared on platforms like Facebook, LinkedIn, Slack, and iMessage. Without them, those platforms guess — and they usually guess wrong.
export const metadata = {
openGraph: {
title: 'My App',
description: 'The best app on the internet.',
url: 'https://example.com',
siteName: 'My App',
images: [
{
url: 'https://example.com/og.jpg',
width: 1200,
height: 630,
alt: 'My App preview image',
},
],
locale: 'en_US',
type: 'website',
},
};Image requirements
The standard OG image size is 1200 x 630 pixels. Most platforms crop or scale images that do not match. Use a .jpg or .png with a file size under 8 MB. Always include an alt description for accessibility.
/public/og.jpg and reference it as https://yoursite.com/og.jpg. Do not use a relative path — social crawlers cannot resolve them.Build your Open Graph metadata in seconds.
Try the Builder →Twitter / X Cards
Twitter has its own metadata format that runs parallel to Open Graph. If you omit Twitter-specific tags, Twitter will fall back to OG tags — but it is better to be explicit.
export const metadata = {
twitter: {
card: 'summary_large_image',
title: 'My App',
description: 'The best app on the internet.',
images: ['https://example.com/twitter.jpg'],
site: '@mybrand',
creator: '@janedoe',
},
};Card types
There are four Twitter card types. For most sites, summary_large_image is the right choice — it shows a big image above the tweet and gets significantly more engagement than the smaller summary card.
summary_large_image— Large image card. Best for articles, tools, and landing pages.summary— Small square image with text. Good for product pages.app— Used to promote App Store or Google Play apps.player— Embeds an inline video or audio player.
Robots — Controlling indexing and crawling
The robots object tells search engine crawlers what they are and are not allowed to do on a given page. In most cases you want both index and follow set to true. Override them on pages like thank-you pages, staging routes, or admin dashboards.
export const metadata = {
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-image-preview': 'large',
'max-snippet': -1,
'max-video-preview': -1,
},
},
};The googleBot sub-object gives Google-specific directives. Setting max-snippet: -1 allows Google to use any snippet length in search results, which typically improves click-through rates for blog and documentation pages.
index: false at the root layout.js level will de-index your entire site. Use it only on specific pages, never on a shared layout.Canonical URLs — Preventing duplicate content
A canonical URL tells search engines which version of a page is the one to index. This matters when the same content is accessible at multiple URLs — for example, with and without a trailing slash, or through both www and non-www domains.
export const metadata = {
alternates: {
canonical: 'https://www.example.com/blog/my-post',
},
};Always use the full absolute URL, including the protocol and www (or lack of it, depending on your canonical domain). Relative paths are not supported here and will be silently ignored by some crawlers.
Set your canonical URL — and every other metadata field — in one place.
Go to the Builder →Site verification tags
Search consoles from Google, Bing, and Yandex require you to verify site ownership. Next.js lets you add these tokens cleanly through the verification object, rather than placing raw meta tags in your layout.
export const metadata = {
verification: {
google: 'your-google-verification-code',
yandex: 'your-yandex-verification-code',
yahoo: 'your-yahoo-verification-code',
other: {
'msvalidate.01': 'your-bing-code', // Bing
},
},
};Find your verification code inside Google Search Console under Settings → Ownership verification → HTML tag. Copy only the content value from the meta tag — not the full tag itself.
Dynamic metadata with generateMetadata
For pages where the metadata depends on data — like a blog post or product page — use the generateMetadata async function instead of a static export. It receives the same params and searchParams as your page component.
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [{ url: post.coverImage }],
},
};
}Next.js automatically deduplicates fetch calls made inside generateMetadata and the page component itself, so you do not need to worry about extra network requests.
Build and copy your static metadata object right now.
Open the Builder →