Static metadata works for fixed pages, but a blog post's title or a product's description needs metadata generated from real, fetched data. This lesson covers generateMetadata, the async counterpart to the static metadata export.
generateMetadata: Metadata from Fetched Data
generateMetadata is an async function you export from a page.tsx or layout.tsx, receiving the same params (and searchParams, for pages) that the route component itself receives. Whatever object it returns is used exactly like a static metadata export.
Because it's async, you can fetch the exact content the page is about to render — a blog post, a product, a user profile — and build metadata directly from that real data, ensuring titles and descriptions always match the actual page content.
generateMetadata runs before the page component renders, and can be async.
It receives the same params (and searchParams on pages) as the route component.
Identical fetch() calls made in both generateMetadata and the page are automatically deduplicated.
A route can export either a static metadata object or generateMetadata, but not both.
Dynamic Metadata Cheat Sheet
When to use generateMetadata over the static export.
Situation
Approach
Fixed page content (About, Contact)
Static metadata export
Blog post, product, or profile page
generateMetadata
Title depends on fetched data
generateMetadata
Handling a missing resource (404)
Call notFound() inside generateMetadata or the page
Handling Missing Content Gracefully
If the underlying data doesn't exist — an invalid blog slug, for example — generateMetadata should return sensible fallback metadata (or the page itself should call notFound()), rather than throwing an unhandled error or returning undefined values that produce blank tags.
export async function generateMetadata({
params,
}: {
params: { slug: string };
}) {
const post = await getPostBySlug(params.slug);
if (!post) {
return { title: "Post Not Found" };
}
return { title: post.title, description: post.excerpt };
}
Reading Parent Metadata
generateMetadata can accept a second argument, parent, giving access to the resolved metadata from parent layouts — useful when you want to extend rather than fully replace an inherited value.
Common Mistakes
Fetching the same data in generateMetadata and the page component with slightly different arguments, defeating automatic deduplication.
Not handling missing/invalid data inside generateMetadata, leaving blank or broken metadata tags.
Trying to export both metadata and generateMetadata from the same file.
Forgetting generateMetadata runs on the server and cannot use client-only data.
Key Takeaways
generateMetadata builds metadata dynamically from fetched data, unlike the static metadata export.
It receives the same params/searchParams as the route component.
Identical fetch calls between generateMetadata and the page are deduplicated automatically.
Always handle missing/invalid content gracefully within generateMetadata.
A route uses either metadata or generateMetadata, never both.
Pro Tip
Extract your data-fetching function (like getPostBySlug) into a shared helper used by both generateMetadata and the page component — writing the exact same fetch call in both places, with identical arguments, is what enables Next.js's automatic request deduplication to actually kick in.
You now understand dynamic metadata. Next, learn how to generate a sitemap.xml file so search engines can discover every page on your site.