Next.js Metadata and SEO

Set page titles, descriptions, Open Graph images, sitemaps, and robots rules with the Next.js Metadata API.

TL;DR

  1. Export a metadata object from a layout or page for static tags.
  2. Export generateMetadata() when the title depends on fetched data.
  3. Add sitemap.ts and robots.ts in app to guide search engines.

Static Metadata

    metadata

    Export this object from a layout or page to set its title and description.

    import type { Metadata } from 'next';
    
    export const metadata: Metadata = {
      title: 'Acme Store',
      description: 'Handmade goods, shipped worldwide.',
    };
    title.template

    Add your site name to every page title from the root layout.

    export const metadata: Metadata = {
      title: {
        default: 'Acme Store',
        template: '%s | Acme Store',
      },
    };
    metadataBase

    Set the base URL so relative image and canonical paths become full URLs.

    export const metadata: Metadata = {
      metadataBase: new URL('https://acme.com'),
    };

Dynamic Metadata

    generateMetadata()

    Build the title from fetched data for pages with dynamic content.

    export async function generateMetadata(
      props: PageProps<'/blog/[slug]'>
    ): Promise<Metadata> {
      const { slug } = await props.params;
      const post = await getPost(slug);
      return { title: post.title };
    }
    alternates.canonical

    Point search engines to the main URL when a page has several addresses.

    return {
      title: post.title,
      alternates: {
        canonical: `/blog/${slug}`,
      },
    };
    robots

    Keep one page out of search results by setting index to false.

    export const metadata: Metadata = {
      robots: { index: false, follow: false },
    };

Social Sharing

    openGraph

    Control the title, text, and image shown when a link is shared.

    export const metadata: Metadata = {
      openGraph: {
        title: 'Acme Store',
        description: 'Handmade goods.',
        images: ['/og.png'],
      },
    };
    twitter

    Request the large image card when a link is shared on X.

    export const metadata: Metadata = {
      twitter: {
        card: 'summary_large_image',
        images: ['/og.png'],
      },
    };
    opengraph-image.tsx

    Generate the share image from code by adding this file to a route.

    // app/opengraph-image.tsx
    import { ImageResponse } from 'next/og';
    
    export const size = { width: 1200, height: 630 };
    
    export default function Image() {
      return new ImageResponse(
        <div style={{ fontSize: 64 }}>Acme Store</div>,
        size
      );
    }

Sitemap and Structured Data

    sitemap.ts

    Return your URLs from this file to serve them at /sitemap.xml.

    // app/sitemap.ts
    import type { MetadataRoute } from 'next';
    
    type Sitemap = MetadataRoute.Sitemap;
    
    export default function sitemap(): Sitemap {
      return [
        { url: 'https://acme.com' },
        { url: 'https://acme.com/blog' },
      ];
    }
    robots.ts

    Tell crawlers what they may visit and where to find your sitemap.

    // app/robots.ts
    import type { MetadataRoute } from 'next';
    
    type Robots = MetadataRoute.Robots;
    
    export default function robots(): Robots {
      return {
        rules: { userAgent: '*', allow: '/' },
        sitemap: 'https://acme.com/sitemap.xml',
      };
    }
    JSON-LD

    Add structured data so search engines can show rich results for a page.

    const jsonLd = {
      '@context': 'https://schema.org',
      '@type': 'Product',
      name: product.name,
    };
    
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{
        __html: JSON.stringify(jsonLd),
      }}
    />

Tips

  1. Set metadataBase once in the root layout, so relative Open Graph image paths resolve to full URLs.
  2. Use a title.template in the root layout, so every page title gets your site name without repeating it.

Warnings

  1. The metadata export only works in Server Components, so it is ignored in any file marked 'use client'.
  2. Awaiting params is required inside generateMetadata(), because it is a promise just like in the page component.

In Practice

FAQ