Lesson 29 +10 XP

Dynamic Metadata & SEO Optimization

Dynamic Metadata & SEO Optimization

Next.js provides a robust Metadata API to manage document head tags (<title>, <meta>, OpenGraph, Twitter cards) for search engine optimization (SEO) and social media sharing.

1. Static Metadata Export

Export a static metadata object from any layout.tsx or page.tsx:

// app/about/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'About Us | TechCorp',
  description: 'Learn about TechCorp and our full-stack engineering team.',
  openGraph: {
    title: 'About Us | TechCorp',
    description: 'Learn about TechCorp and our full-stack engineering team.',
    url: 'https://techcorp.com/about',
    siteName: 'TechCorp',
    images: [{ url: 'https://techcorp.com/og-about.png', width: 1200, height: 630 }],
    locale: 'en_US',
    type: 'website',
  },
};

export default function AboutPage() {
  return <main>About Content</main>;
}

2. Dynamic Metadata Generation (generateMetadata)

For dynamic pages (such as app/blog/[slug]/page.tsx), generate metadata programmatically using generateMetadata():

// app/blog/[slug]/page.tsx
import { Metadata } from 'next';
import db from '@/lib/db';

type Props = {
  params: Promise<{ slug: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await db.post.findUnique({ where: { slug } });

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

  return {
    title: `${post.title} | Tech Blog`,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  };
}

export default async function BlogPostPage({ params }: Props) {
  const { slug } = await params;
  return <article>Article body for {slug}</article>;
}

Dynamic Sitemaps and Robots (sitemap.ts & robots.ts)

Generate sitemaps dynamically by creating app/sitemap.ts:

// app/sitemap.ts
import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  return [
    {
      url: 'https://example.com',
      lastModified: new Date(),
      changeFrequency: 'yearly',
      priority: 1,
    },
    {
      url: 'https://example.com/blog',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.8,
    },
  ];
}

Metadata Rules

  1. Metadata is evaluated from parent to child: Root Layout -> Page Metadata.
  2. Metadata fields are merged; deeper child routes overwrite matching parent title/description fields.

TL;DR

  • Export static metadata using export const metadata: Metadata = { ... }.
  • Export dynamic metadata for variable routes using export async function generateMetadata({ params }).
  • Manage OpenGraph and Twitter cards directly inside the metadata object.
  • Automatically generate sitemaps using app/sitemap.ts.