Dynamic Metadata with generateMetadata
When your page metadata depends on route parameters or fetched data, you need dynamic metadata. Next.js provides the generateMetadata function for this purpose.
The generateMetadata Function
Use generateMetadata when metadata depends on dynamic data:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
type Props = {
params: Promise<{ slug: string }>
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.excerpt,
}
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params
const post = await getPost(slug)
return <article>...</article>
}
Accessing Route Parameters
The params prop contains your dynamic route segments:
// app/products/[category]/[id]/page.tsx
type Props = {
params: Promise<{ category: string; id: string }>
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { category, id } = await params
const product = await getProduct(category, id)
return {
title: `${product.name} - ${category}`,
description: product.shortDescription,
}
}
Accessing Search Parameters
You can also access search params for metadata:
type Props = {
params: Promise<{ slug: string }>
searchParams: Promise<{ page?: string }>
}
export async function generateMetadata({ params, searchParams }: Props): Promise<Metadata> {
const { slug } = await params
const { page } = await searchParams
const post = await getPost(slug)
const title = page
? `${post.title} - Page ${page}`
: post.title
return { title }
}
Fetching Data for Metadata
Data fetched in generateMetadata is automatically deduplicated with the page component:
// This fetch is deduplicated - only called once
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug) // First call
return {
title: post.title,
description: post.excerpt,
}
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params
const post = await getPost(slug) // Reuses cached result
return <article>{post.content}</article>
}
Building Canonical URLs Dynamically
For dynamic routes, build the canonical URL from the slug:
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
const canonicalUrl = `https://example.com/blog/${slug}`
return {
title: post.title,
description: post.excerpt,
alternates: {
canonical: canonicalUrl,
},
}
}
Important: Always use absolute URLs for canonicals.
Error Handling
Handle cases where data doesn't exist:
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
if (!post) {
return {
title: 'Post Not Found',
description: 'The requested post could not be found.',
robots: {
index: false, // Don't index 404 pages
},
}
}
return {
title: post.title,
description: post.excerpt,
}
}
Parent Metadata Inheritance
Child routes can extend parent metadata:
// app/blog/layout.tsx
export const metadata: Metadata = {
title: {
template: '%s | Blog',
default: 'Blog',
},
}
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title, // Renders as "Post Title | Blog"
}
}
Complete Dynamic Metadata Example
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
type Props = {
params: Promise<{ slug: string }>
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
if (!post) {
return {
title: 'Post Not Found',
}
}
const canonicalUrl = `https://example.com/blog/${slug}`
return {
title: post.title,
description: post.excerpt,
authors: [{ name: post.author.name }],
keywords: post.tags,
alternates: {
canonical: canonicalUrl,
},
robots: {
index: true,
follow: true,
},
}
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params
const post = await getPost(slug)
if (!post) {
notFound()
}
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
Summary
In this lesson, you learned:
- How to use
generateMetadatafor dynamic pages - Accessing route and search parameters
- Data fetching and deduplication
- Building canonical URLs dynamically
- Error handling for missing content
- Parent metadata inheritance
In the next lesson, we'll cover Open Graph and Twitter Card metadata for social sharing.

