Next.js

Lesson 09

Metadata per route

Use the Metadata API in Server Components instead of mutating the document head manually.

Good Code

app/articles/[slug]/page.tsx
import type { Metadata } from "next";
import { getArticleBySlug } from "@/lib/articles";

export async function generateMetadata({
  params,
}: PageProps<"/articles/[slug]">): Promise<Metadata> {
  const { slug } = await params;
  const article = await getArticleBySlug(slug);

  // generateMetadata makes head data part of the server route contract.
  return {
    title: article?.title ?? "Article not found",
    description: article?.excerpt ?? "This article is not available.",
  };
}

export default async function ArticlePage({
  params,
}: PageProps<"/articles/[slug]">) {
  const { slug } = await params;
  const article = await getArticleBySlug(slug);

  return <h1>{article?.title}</h1>;
}

Bad Code

app/articles/[slug]/article-title.tsx
"use client";

import { useEffect } from "react";

export function ArticleTitle({ title }: { title: string }) {
  // Updating document.title after hydration is too late for reliable previews.
  useEffect(() => {
    document.title = title;
  }, [title]);

  return <h1>{title}</h1>;
}

Review Notes

What to review

Good Code

The good version lets Next.js generate route metadata from server-side article data.

Bad Code

The bad version updates document.title after hydration, so crawlers and social previews can miss the route-specific metadata.

Takeaways

  • Route metadata should live in metadata exports or generateMetadata.