Next.js provides a Metadata API for managing <title>, <meta>, Open Graph tags, and more — without manually editing HTML <head> elements.

Static Metadata

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

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

export const metadata: Metadata = {
    title: 'About Us',
    description: 'Learn about our team and mission.',
};

export default function AboutPage() {
    return <h1>About Us</h1>;
}
  

Child pages inherit metadata from parent layouts. Page metadata overrides layout metadata.

Root Layout Metadata

Set defaults in the root layout:

  export const metadata: Metadata = {
    title: {
        default: 'My Next.js App',
        template: '%s | My Next.js App',
    },
    description: 'A modern web application built with Next.js.',
    metadataBase: new URL('https://myapp.com'),
};
  

The template adds a suffix to child page titles: “About Us | My Next.js App”.

Dynamic Metadata with generateMetadata

Generate metadata from fetched data:

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

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

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

    return {
        title: post.title,
        description: post.excerpt,
        openGraph: {
            title: post.title,
            description: post.excerpt,
            images: [{ url: post.coverImage, width: 1200, height: 630 }],
        },
    };
}

export default async function BlogPost({ params }: Props) {
    const { slug } = await params;
    const post = await getPost(slug);
    return <article><h1>{post.title}</h1></article>;
}
  

Open Graph and Twitter Cards

Control social sharing previews:

  export const metadata: Metadata = {
    openGraph: {
        title: 'Product Page',
        description: 'Check out our latest product.',
        url: 'https://myapp.com/products/widget',
        siteName: 'My App',
        images: [{ url: 'https://myapp.com/og-image.png', width: 1200, height: 630 }],
        type: 'website',
    },
    twitter: {
        card: 'summary_large_image',
        title: 'Product Page',
        images: ['https://myapp.com/og-image.png'],
    },
};
  

Robots and Canonical URLs

Set robots: { index: true, follow: true } and alternates: { canonical: '...' } in metadata. Use robots: { index: false } to hide pages from search engines.

Sitemap and robots.txt

Generate a sitemap programmatically:

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

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const posts = await getAllPosts();

    return [
        { url: 'https://myapp.com', lastModified: new Date(), priority: 1 },
        ...posts.map(post => ({
            url: `https://myapp.com/blog/${post.slug}`,
            lastModified: post.updatedAt,
            priority: 0.6,
        })),
    ];
}
  

Available at https://myapp.com/sitemap.xml.

  // app/robots.ts
export default function robots() {
    return {
        rules: { userAgent: '*', allow: '/', disallow: ['/admin/'] },
        sitemap: 'https://myapp.com/sitemap.xml',
    };
}
  

Next: add authentication with Auth.js.