generateMetadata
Export an async generateMetadata function to build metadata from dynamic data like route params.
Syntax
export async function generateMetadata({ params }): Promise<Metadata>When a page's title or description depends on data — a blog post title, a product name — export an async generateMetadata function instead of a static object. It receives the same params as your page and can fetch whatever it needs.
Example
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
};
}
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <h1>{slug}</h1>;
}When to use it
- A blog post page uses generateMetadata to fetch the post title from the CMS and use it as the page title.
- A product page generates unique Open Graph titles and images per SKU by reading the id param in generateMetadata.
- An error page produces a generic title while a found page produces a title from the database record — both handled by one generateMetadata function.
More examples
Fetch title from CMS
generateMetadata is async and receives the same params as the page, enabling dynamic title generation.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post?.title ?? 'Post not found',
description: post?.excerpt,
};
}Unique OG image per product
Returning openGraph.images from generateMetadata gives each product its own social sharing card.
// app/products/[id]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata(
{ params }: { params: Promise<{ id: string }> }
): Promise<Metadata> {
const { id } = await params;
const product = await getProduct(id);
return {
title: product.name,
openGraph: {
images: [{ url: product.imageUrl }],
},
};
}Deduplicated fetch with cache
Next.js deduplicates identical fetch calls between generateMetadata and the page component, avoiding double requests.
// app/blog/[slug]/page.tsx
async function getPost(slug: string) {
return fetch(`/api/posts/${slug}`).then(r => r.json());
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug); // deduped with page's fetch
return { title: post.title };
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug); // same request, served from cache
return <h1>{post.title}</h1>;
}
Discussion