Back to Blog
Lesson 10 of the Next.js: Build Full-Stack Apps with the App Router course
Next.jsJuly 28, 20264 min read

Generating Dynamic Metadata: Next.js SEO for Dynamic Routes

Learn how to implement generateMetadata in Next.js to provide unique, SEO-friendly titles and descriptions for your dynamic blog routes.

Next.jsSEOMetadataDynamic RoutesApp Router
Aerial view of a busy multi-lane highway in Madrid, Spain during dusk, showcasing a dynamic travel scene.

Previously in this course, we covered Introduction to Dynamic Routes in Next.js for Blog Posts, where we set up the [slug] folder structure to render unique content based on the URL. While your content is now dynamic, your page titles and descriptions are likely still static, which hurts your search engine visibility.

In this lesson, we add generateMetadata to our dynamic routes. This allows us to fetch data for a specific post and inject it into the <head> of our document, ensuring Google and social media platforms display accurate information for every post.

Why Dynamic Metadata Matters for SEO

In Building the Root Layout: Metadata, Fonts, and HTML in Next.js, we defined static metadata for the entire site. However, search engines rank pages based on unique, descriptive content. If every blog post has the same title tag, search engines struggle to differentiate your content, leading to lower rankings.

By using generateMetadata, we can lift data from our database or CMS and reflect it in the browser tab and search results. This is crucial for SEO and social sharing (Open Graph images, Twitter cards).

Implementing generateMetadata

Vintage letter stamps and hammer on wood surface with 'SUBSCRIBE' on metal sheet.

In the App Router, Next.js provides a special function called generateMetadata. If you export this function from a page.js or layout.js file, Next.js will call it before rendering the page, wait for it to resolve, and then populate the metadata.

Here is how you implement it in your dynamic app/blog/[slug]/page.js:

JAVASCRIPT
// app/blog/[slug]/page.js

// 1. Define your fetch logic(or reuse a helper)
async function getPost(slug) {
  // In a real app, this would be a DB call
  return { title: CE9178">`Post about ${slug}`, description: "Read more here." };
}

// 2. Export the function
export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);

  return {
    title: post.title,
    description: post.description,
  };
}

export default async function Page({ params }) {
  const { slug } = await params;
  return <h1>Post: {slug}</h1>;
}

Key Rules for generateMetadata

  • Async execution: It must be an async function because you almost always need to fetch data.
  • Params access: It receives the same params object as your page.js component.
  • Return object: It must return a metadata object (the same shape as the metadata export).
  • Caching: Next.js automatically memoizes the data fetch if you call the same function in both generateMetadata and your page component. You don't need to worry about the performance cost of fetching the data twice.

Worked Example: Improving the Blog Project

Let’s advance our running blog project. Assuming you have a getPostBySlug function, here is how you would wire it up to ensure your SEO is production-ready.

JAVASCRIPT
// app/blog/[slug]/page.js

export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);

  if (!post) {
    return { title: CE9178">'Post Not Found' };
  }

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: CE9178">'article',
    },
  };
}

Practice Exercise

  1. Open your app/blog/[slug]/page.js file.
  2. If you haven't already, ensure your getPostBySlug function is exported from a data utility file.
  3. Add the generateMetadata function to your file.
  4. Dynamically return the title and description from your data object.
  5. Save and inspect the page source in your browser to verify that the <title> and <meta name="description"> tags have updated.

Common Pitfalls

  • Forgetting to await params: In newer versions of Next.js, params is a Promise. Ensure you use await params before accessing properties like slug.
  • Returning an incomplete object: If your fetch fails, make sure you return default metadata. Returning undefined can cause issues with default metadata inheritance.
  • Over-fetching: While Next.js deduplicates requests, be mindful of complex database queries inside generateMetadata. Keep them focused on the fields required for SEO.

FAQ

Does generateMetadata replace static metadata? No, it complements it. The root layout provides defaults, and generateMetadata in your dynamic segments overrides or extends them.

Can I use client components in generateMetadata? No. generateMetadata is a server-side-only function. It cannot use hooks like useParams or useState.

How do I test my metadata? Use the Open Graph Preview Tool or simply view the page source (Ctrl+U) to verify the meta tags are rendered correctly in the HTML.

Recap

Wooden Scrabble tiles spelling 'RECAP' on a brown textured background. Text concept image.

We’ve successfully moved from static page titles to dynamic SEO management. By exporting generateMetadata, we ensure that every blog post is uniquely identified by search engines. This is a critical step in building a professional-grade application that ranks well and provides clear context to users.

Up next: Managing Shared UI with Layouts — we'll learn how to nest layouts to keep your blog sidebar and navigation consistent across your posts.

Similar Posts