Back to Blog
Next.jsJuly 4, 20264 min read

Next.js generateMetadata: Dynamic SEO for App Router

Master next.js generatemetadata to handle dynamic SEO tags in the App Router. Learn how to fetch server-side data for optimized, crawler-friendly pages.

Next.jsSEOApp RouterWeb DevelopmentMetadata

When we migrated our main marketing dashboard to the App Router, we hit a wall with SEO. Static metadata exports are great for simple landing pages, but they fall apart the second you need dynamic content like blog titles or product descriptions. I spent about two days refactoring our routing logic to ensure every dynamic route had relevant, server-rendered SEO tags.

Understanding Next.js generateMetadata

The generateMetadata function is the standard way to handle dynamic metadata in the App Router. Unlike static metadata objects, this function runs on the server, allowing you to fetch data from your database or CMS before the page renders.

It’s important to remember that generateMetadata runs in parallel with your page component's data fetching. If you're fetching the same data twice—once for the metadata and once for the page—you're wasting resources. Next.js automatically dedupes these requests if you use fetch with the same URL, which is a massive win for performance.

Implementing Dynamic Metadata

To implement next.js generatemetadata for a dynamic route like /products/[id], you need to export an async function from your page file. Here is how I structured it for a recent project:

TSX
// app/products/[id]/page.tsx
import { getProduct } from CE9178">'@/lib/api';

type Props = {
  params: { id: string };
};

export async function generateMetadata({ params }: Props) {
  const product = await getProduct(params.id);

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

  return {
    title: product.name,
    description: product.summary,
    openGraph: {
      images: [product.imageUrl],
    },
  };
}

export default async function ProductPage({ params }: Props) {
  const product = await getProduct(params.id);
  // Render your page...
}

The "Wrong Turn" We Took

Initially, I tried to pass data from the generateMetadata function into the page component using React Context. That was a mistake. generateMetadata and your Page component run in separate rendering lifecycles.

Instead, rely on the native fetch request memoization. If you call getProduct(id) in both functions, Next.js caches the result for the duration of the request. This keeps your next.js app router seo strategy clean without bloated state management or unnecessary prop drilling.

Comparison: Static vs. Dynamic Metadata

FeatureStatic MetadataDynamic Metadata
Data SourceHardcodedAsync (API/DB)
PerformanceInstantRequest-dependent
Use CaseHome, About, ContactArticles, Products, Profiles
ExecutionBuild-timeRequest-time

Advanced Considerations for Server Components SEO

When you're dealing with server components seo, you have to watch out for latency. If your CMS is slow, your generateMetadata call will block the initial HTML response. I usually recommend setting a reasonable timeout or implementing a fallback state if the metadata fetch takes longer than roughly 300ms.

If your metadata depends on complex data, you might be tempted to use Next.js App Router: Implementing Granular Cache Revalidation to ensure SEO tags stay fresh. Just be careful; revalidating too aggressively can lead to cache misses that hurt your Core Web Vitals.

For multi-tenant applications, ensure your metadata logic respects the tenant context. If you're building a platform that serves multiple brands, check out Next.js Multi-tenancy: Implementing Tenant-Aware Data Sharding to see how to scope your metadata fetches correctly.

FAQ

Can I use generateMetadata in layout files? Yes, but be careful. Metadata in layouts is inherited. If you define it in a nested layout, it will override the parent's metadata.

Does generateMetadata support client-side navigation? Yes. When a user navigates between pages, Next.js will call generateMetadata on the server and update the document head automatically.

How do I handle missing metadata for dynamic routes? Always return a default object in your generateMetadata function to ensure crawlers don't get an empty title or description if your database fetch returns null.

Final Thoughts

Getting dynamic metadata next.js right is less about the API and more about managing your data fetching lifecycle. Don't over-engineer it—keep your logic inside the page file, rely on native request memoization, and keep an eye on your external API latency. I'm still experimenting with moving some of these tags to edge functions to shave off a few more milliseconds, but for now, the standard App Router approach is holding up well.

Similar Posts