generateMetadata

Export an async generateMetadata function to build metadata from dynamic data like route params.

Syntaxexport 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

Example · typescript
// 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.

Example · ts
// 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.

Example · ts
// 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.

Example · ts
// 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

  • Be the first to comment on this lesson.