NextjsMetadata & SEO Overview

Metadata & SEO Overview

Before the App Router, getting SEO right in a React app meant hand-managing a pile of <head> tags yourself — usually with a third-party library, careful ordering, and a lot of copy-pasted boilerplate for every page. The App Router replaces all of that with a built-in Metadata API: you describe what a page's title, description, and social preview should be as plain data, and Next.js generates the correct, deduplicated <head> tags for you.
This matters for two audiences at once. Search engines read your title and description to decide how to list your page in results. Social platforms and chat apps (Slack, X, LinkedIn, WhatsApp) read your Open Graph and Twitter Card tags to build the preview card shown when someone shares your link. Getting either wrong — or leaving them blank — means a worse-looking result in search, and a blank or broken preview card everywhere else.
Two ways to define metadata
Every route segment (a layout.tsx or page.tsx) can export metadata in one of two forms, depending on whether the content is known ahead of time or depends on data you fetch.

Approach

Export

Use when

Static metadata

a plain metadata object

The title/description are fixed text you can write by hand — an About page, a pricing page, the homepage.

Dynamic metadata

an async generateMetadata() function

The title/description depend on fetched data — a blog post, a product page, a user profile.

Static — app/about/page.tsx

TSX
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'About Us',
  description: 'Learn what we do and why we do it.',
}

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

Dynamic — app/blog/[slug]/page.tsx

TSX
import type { Metadata } from 'next'

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

  return {
    title: post.title,
    description: post.excerpt,
  }
}

export default async function BlogPostPage({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPost(params.slug)
  return <article>{post.title}</article>
}
The full details of each approach — including title templates, Open Graph fields, and the exact shape of generateMetadata— are covered in their own dedicated pages next in this section.
Metadata is merged down the route tree
Metadata isn't only a page-level concept — a layout.tsx can export metadata too, and it applies to every page nested inside it. Next.js walks the route tree from the root layout down to the matched page, merging metadata objects along the way. A field a page defines overrides the same field from a parent layout; a field the page leaves out is inherited unchanged.

app/layout.tsx

TSX
export const metadata = {
  title: 'My Site',
  description: 'The default description for every page on the site.',
}

app/blog/page.tsx

TSX
export const metadata = {
  title: 'Blog',
  // description is not set here, so the root layout's
  // description is inherited as-is.
}
Note
Merging is per-field, not all-or-nothing — but a few fields (like title when using a template, covered on the next page) have their own special composition rules rather than a simple overwrite. Arrays and objects such as openGraph are replaced wholesale by the closest segment that defines them, not deep-merged key by key.
  • The Metadata API replaces manual <head> management with declarative data.

  • Static metadata objects suit fixed content; generateMetadata() suits content built from fetched data.

  • Metadata can be exported from both layout.tsx and page.tsx, and is merged down the tree.

  • A closer segment overrides the same metadata field from a parent; unset fields are inherited.

  • Getting this right improves both search engine listings and social share previews.