patterntypescriptnextjsModerate
Metadata API: static and dynamic page metadata in App Router
Viewed 0 times
Next.js 13.2+ for Metadata API
metadata APIgenerateMetadatapage titleSEOopen graphtitle template
Problem
Pages Router used next/head with JSX to set page titles and meta tags. App Router uses a completely different Metadata API with exported objects and functions, and using next/head in App Router doesn't work.
Solution
Export a metadata object or generateMetadata function from page.tsx or layout.tsx:
// Static metadata — app/about/page.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About Us',
description: 'Learn about our company',
openGraph: {
title: 'About Us | Acme Corp',
images: [{ url: '/og-about.png', width: 1200, height: 630 }],
},
};
// Dynamic metadata — app/blog/[slug]/page.tsx
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const post = await fetchPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [{ url: post.coverImage }],
},
};
}
// Root layout sets site-wide defaults and template
// app/layout.tsx
export const metadata: Metadata = {
title: { template: '%s | Acme Corp', default: 'Acme Corp' },
description: 'Default description',
};
// Static metadata — app/about/page.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About Us',
description: 'Learn about our company',
openGraph: {
title: 'About Us | Acme Corp',
images: [{ url: '/og-about.png', width: 1200, height: 630 }],
},
};
// Dynamic metadata — app/blog/[slug]/page.tsx
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const post = await fetchPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [{ url: post.coverImage }],
},
};
}
// Root layout sets site-wide defaults and template
// app/layout.tsx
export const metadata: Metadata = {
title: { template: '%s | Acme Corp', default: 'Acme Corp' },
description: 'Default description',
};
Why
The Metadata API enables Next.js to render meta tags on the server for SEO and social sharing without JavaScript. The title template system cascades through layouts, eliminating redundant title strings. next/head in App Router is not supported and will be silently ignored.
Gotchas
- generateMetadata runs on the server — it can fetch data but must be async
- Metadata defined in layout.tsx applies to all child pages unless overridden
- The title template '%s' is replaced by the child page's title string
- Twitter card metadata is nested under twitter: {} in the Metadata object
Code Snippets
Root layout metadata with title template
export const metadata: Metadata = {
title: { template: '%s | My Site', default: 'My Site' },
metadataBase: new URL('https://mysite.com'),
openGraph: { type: 'website' },
};Context
When setting page titles, descriptions, and Open Graph tags in Next.js App Router
Revisions (0)
No revisions yet.