The most surprising thing about Next.js metadata is how deeply it integrates with React Server Components, allowing you to generate dynamic meta tags before the client even sees the HTML.

Let’s see it in action. Imagine you have a blog post. You want its title and description to appear perfectly in search results and social shares.

Here’s a simple app/page.tsx:

import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My Awesome Blog',
  description: 'Welcome to the best place to learn about Next.js SEO.',
  openGraph: {
    title: 'My Awesome Blog - The Next.js SEO Hub',
    description: 'Discover how to master Next.js metadata for perfect meta tags.',
    images: ['/images/og-image.png'],
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    title: 'My Awesome Blog - Next.js SEO Mastery',
    description: 'Unlock the secrets of Next.js metadata API for impeccable search and social previews.',
    images: ['/images/twitter-image.png'],
  },
};

export default function Page() {
  return (
    <div>
      <h1>Welcome to the Blog!</h1>
      <p>Explore our latest posts.</p>
    </div>
  );
}

When a crawler or social media bot hits this page, Next.js, running on the server, will execute this metadata object. It generates the full HTML <head> section, including:

<head>
  <title>My Awesome Blog</title>
  <meta name="description" content="Welcome to the best place to learn about Next.js SEO." />
  <meta property="og:title" content="My Awesome Blog - The Next.js SEO Hub" />
  <meta property="og:description" content="Discover how to master Next.js metadata for perfect meta tags." />
  <meta property="og:image" content="/images/og-image.png" />
  <meta property="og:type" content="website" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="My Awesome Blog - Next.js SEO Mastery" />
  <meta name="twitter:description" content="Unlock the secrets of Next.js metadata API for impeccable search and social previews." />
  <meta name="twitter:image" content="/images/twitter-image.png" />
  <!-- ... other default tags like viewport, charset -->
</head>

This is powerful because the meta tags are server-rendered. They are part of the initial HTML response, not loaded via JavaScript on the client. This means search engines and social platforms get the rich information they need immediately, improving indexing and preview accuracy.

The Metadata API is designed to be composable. You can define default metadata in a root app/layout.tsx and then override or extend it in specific route segments.

Here’s app/layout.tsx:

import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: {
    default: 'My Awesome Blog',
    template: '%s | My Awesome Blog',
  },
  description: 'Your go-to source for Next.js development.',
  icons: {
    icon: '/favicon.ico',
    apple: '/apple-icon.png',
  },
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

With this root layout, the app/page.tsx example above would render a title like "My Awesome Blog | My Awesome Blog" because the template in the root layout wraps the title from the page. If the page only had a description, the title would simply be "My Awesome Blog". This templating is crucial for consistent branding.

You can also generate metadata dynamically using functions. This is where it gets really interesting for things like individual blog posts or product pages.

Consider app/posts/[slug]/page.tsx:

import type { Metadata } from 'next';

interface PostPageParams {
  slug: string;
}

async function getPostData(slug: string) {
  // In a real app, fetch from a CMS or database
  const posts = {
    'hello-world': {
      title: 'Hello World!',
      description: 'The very first post on our blog.',
      ogImage: '/images/og-hello.png',
    },
    'nextjs-metadata': {
      title: 'Mastering Next.js Metadata',
      description: 'A deep dive into the Next.js Metadata API.',
      ogImage: '/images/og-metadata.png',
    },
  };
  return posts[slug as keyof typeof posts];
}

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

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

  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: `${post.title} | My Awesome Blog`,
      description: post.description,
      images: [post.ogImage],
      type: 'article',
    },
    twitter: {
      card: 'summary_large_image',
      title: `${post.title} | Next.js SEO`,
      description: post.description,
      images: [post.ogImage],
    },
  };
}

export default async function PostPage({ params }: { params: PostPageParams }) {
  const post = await getPostData(params.slug);

  if (!post) {
    return <h1>Post not found</h1>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.description}</p>
    </div>
  );
}

Here, generateMetadata is an async function that fetches data based on the slug from the URL. It then returns a Metadata object, dynamically creating unique meta tags for each post. This means each blog post will have its own title, description, and social media preview images, all generated server-side.

The metadata object is merged from the root layout down. Properties defined in a child route segment will override properties with the same name defined in a parent segment. For example, if app/posts/[slug]/page.tsx defines title and app/posts/layout.tsx also defines title, the title from app/posts/[slug]/page.tsx will be used for that specific post page.

One crucial aspect is understanding how Next.js handles different types of metadata. While basic title and description are straightforward, the openGraph and twitter objects are specifically for social sharing previews. They are distinct from the primary title and description used by search engines for their main search result snippets. You can have a page title of "Best Coffee Makers" for SEO, but an Open Graph title of "My Coffee Blog - The Ultimate Guide to Brewing" for a more engaging social share.

The next step in mastering SEO with Next.js is exploring advanced routing patterns like dynamic segments and how they interact with generateMetadata for complex sitemaps and rich previews across vast content catalogs.

Want structured learning?

Take the full Nextjs course →