Skip to content

The Metadata API

The Metadata API is the App Router's built-in system for setting <title>, <meta>, and Open Graph tags per route, replacing the need to manually manage a <head> tag or a third-party library. This lesson covers static and dynamic metadata.

Static Metadata with the metadata Export

Any layout.tsx or page.tsx can export a metadata object containing a title, description, and many other fields. Next.js reads this object at build/render time and injects the corresponding tags into the document <head> automatically — you never write a <head> tag yourself in the App Router.

Metadata from nested layouts and pages merges together, with more specific (deeper) values taking priority — so a root layout might set a default title.template, while individual pages just set their own specific title.

// app/about/page.tsx
export const metadata = {
  title: "About Us",
  description: "Learn about our company's mission and team.",
};

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

Next.js generates <title>About Us</title> and the matching <meta name="description"> tag automatically.

Static Metadata Object Shape

export const metadata = {
  title: "Page Title",
  description: "Page description for search engines.",
  openGraph: {
    title: "Page Title",
    description: "Shown when shared on social media.",
    images: ["/og-image.png"],
  },
};
  • title and description are the most commonly used fields, mapping directly to <title>/<meta name="description">.
  • openGraph controls how the page appears when shared on social platforms like Facebook or LinkedIn.
  • Metadata exports must use plain, static values — for dynamic values, use generateMetadata instead.
  • A root layout can define a title.template (like "%s | My Site") that pages fill in automatically.

Metadata Fields Cheat Sheet

Commonly used fields in the metadata object.

Field Purpose
title Page <title> tag
description <meta name="description">
openGraph Social sharing preview data
twitter Twitter/X-specific sharing card data
robots Per-page indexing directives
alternates.canonical Canonical URL for duplicate content

Title Templates in the Root Layout

Setting a title.template once in the root layout lets every page's own title get consistently formatted (e.g. appending your site name) without repeating that suffix in every single page's metadata export.

// app/layout.tsx
export const metadata = {
  title: {
    template: "%s | My Company",
    default: "My Company",
  },
};

// app/about/page.tsx
export const metadata = { title: "About" }; // renders as "About | My Company"

How Metadata Merges Across Layouts

Metadata fields merge shallowly: a page's metadata.openGraph entirely replaces a parent layout's openGraph object (rather than merging field by field), while unrelated fields like title are inherited independently. Understanding this shallow-merge behavior helps avoid surprises when a page seems to lose some inherited fields.

Common Mistakes

  • Exporting a metadata object with dynamic, per-request values instead of using generateMetadata.
  • Forgetting that nested openGraph (and similar object fields) replace, rather than merge with, a parent's values.
  • Not setting a title.template in the root layout, leading to inconsistent title formatting across pages.
  • Leaving default, generic metadata on every page instead of writing unique titles and descriptions.

Key Takeaways

  • The metadata export sets <title>, <meta>, and Open Graph tags declaratively, per route.
  • Metadata merges from the root layout down to the page, with deeper values taking priority.
  • title.template in the root layout standardizes title formatting across the whole site.
  • Object fields like openGraph replace (rather than merge into) a parent's value when overridden.
  • Use generateMetadata (covered next) for metadata that depends on dynamic data.

Pro Tip

Set a sensible title.template and a fallback description in your root layout on day one — this guarantees every route has reasonable metadata by default, even ones you haven't gotten around to customizing yet.