---
title: "Metadata Generation"
description: "Metadata generation in Next.js provides a comprehensive, declarative system for defining, inheriting, and resolving document metadata and viewport configurations across the App Router hierarchy. By..."
last_updated: "2026-09-23T10:52:03.167933+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/metadata-generation"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/next/src/lib/metadata/resolve-metadata.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts)
- [packages/next/src/lib/metadata/metadata.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx)
- [packages/next/src/lib/metadata/types/metadata-interface.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts)
- [packages/next/src/lib/metadata/get-metadata-route.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts)
- [packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts)
- [packages/next/src/lib/metadata/resolvers/resolve-basics.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-basics.ts)
- [packages/next/src/lib/metadata/is-metadata-route.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts)
- [packages/next/src/server/lib/router-utils/filesystem.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/filesystem.ts)
- [packages/next/src/export/routes/app-page.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-page.ts)
- [packages/next/src/lib/metadata/types/metadata-types.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-types.ts)
- [packages/next/src/lib/metadata/resolvers/resolve-icons.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-icons.ts)
- [packages/next/src/lib/metadata/resolvers/resolve-url.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-url.ts)
- [packages/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-04.output.tsx](https://github.com/blade47/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-04.output.tsx)
- [packages/next/src/lib/metadata/resolvers/resolve-title.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-title.ts)
- [packages/next/src/lib/metadata/default-metadata.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/default-metadata.tsx)
- [packages/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-03.output.tsx](https://github.com/blade47/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-03.output.tsx)
- [packages/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/access-props-19.output.tsx](https://github.com/blade47/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/access-props-19.output.tsx)
- [packages/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-04.input.tsx](https://github.com/blade47/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-04.input.tsx)
- [packages/next/src/lib/metadata/types/icons.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/icons.ts)
- [packages/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-01.output.tsx](https://github.com/blade47/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-01.output.tsx)
- [packages/next/src/lib/metadata/metadata-context.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata-context.tsx)
- [packages/next/src/shared/lib/router/utils/route-regex.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts)
- [packages/next/src/shared/lib/segment.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/segment.ts)
</details>

## Overview

Metadata generation in Next.js provides a comprehensive, declarative system for defining, inheriting, and resolving document metadata and viewport configurations across the App Router hierarchy. By supporting both static metadata objects and dynamic asynchronous `generateMetadata` functions in Server Components, the system eliminates manual head-tag management while enforcing correct TypeScript contracts. It handles complex layout accumulation, default fallbacks, specialized social card processing, and URL resolution against `metadataBase`. Furthermore, it integrates tightly with the routing engine to identify special metadata files and compile route regular expressions, ultimately serializing processed metadata into React server head elements and static export artifacts. Sources: [packages/next/src/lib/metadata/resolve-metadata.ts:1-87](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts#L1-L87), [packages/next/src/lib/metadata/metadata.tsx:35-63](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L35-L63), [packages/next/src/lib/metadata/types/metadata-interface.ts:1-14](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L1-L14), [packages/next/src/lib/metadata/get-metadata-route.ts:107-160](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L107-L160)

## Metadata API and Type Definitions

### Overview

The Metadata API establishes public contracts and TypeScript interfaces for configuring document headers and viewport properties through static exports or dynamic asynchronous generation functions in Server Components. Next.js enforces strict separation: static `metadata` objects and `generateMetadata` functions are supported exclusively in Server Components, and routes must not export both a `metadata` object and a `generateMetadata` function from the same segment.

Sources: [packages/next/src/lib/metadata/types/metadata-interface.ts:1-14](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L1-L14)

### Asynchronous Metadata Generation Signatures

Dynamic metadata uses `generateMetadata` functions that receive segment properties containing asynchronous promises for route parameters and search parameters. Because dynamic parameters are asynchronous in the App Router architecture, functions must await `props.params` or `props.searchParams` before accessing dynamic route segments.

```typescript
type MetadataProps = {
  params: Promise<{ slug: string }>
}

export async function generateMetadata(props: MetadataProps) {
  return {
    title: (await props.params).slug,
  }
}
```

Sources: [packages/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-03.output.tsx:1-10](https://github.com/blade47/next-codemod/transforms/__testfixtures__/next-async-request-api-dynamic-props/generate-metadata-access-prop-03.output.tsx#L1-L10)

> [!WARNING]
> Do not export both a static `metadata` object and a dynamic `generateMetadata` function from the same route segment, as this creates an ambiguous resolution conflict.
> Sources: [packages/next/src/lib/metadata/types/metadata-interface.ts:8-9](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L8-L9)

### Core Interface Contracts

The metadata subsystem defines structured types for standard HTML meta attributes, OpenGraph data, Twitter cards, alternative URLs, verification tokens, and viewport configurations.

| Interface / Type | Description | Key Properties | Sources Reference |
| --- | --- | --- | --- |
| `Metadata` | Public configuration object for static exports | `title`, `description`, `applicationName`, `authors`, `generator`, `keywords`, `referrer`, `creator`, `publisher`, `robots`, `alternates`, `icons`, `openGraph`, `manifest`, `twitter`, `facebook`, `pinterest`, `verification`, `appleWebApp`, `formatDetection`, `itunes`, `abstract`, `appLinks`, `category`, `classification`, `other` | [packages/next/src/lib/metadata/types/metadata-interface.ts:540-564](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L540-L564) |
| `Viewport` | Viewport configuration contract | `width`, `height`, `initialScale`, `minimumScale`, `maximumScale`, `userScalable`, `viewportFit`, `interactiveWidget`, `themeColor`, `colorScheme` | [packages/next/src/lib/metadata/types/metadata-interface.ts:765-797](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L765-L797) |
| `Author` | Author structure for author metadata | `name`, `url` | [packages/next/src/lib/metadata/types/metadata-types.ts:38-43](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-types.ts#L38-L43) |
| `IconDescriptor` | Structured icon descriptor | `url`, `type`, `sizes`, `color`, `rel`, `media`, `fetchPriority` | [packages/next/src/lib/metadata/types/metadata-types.ts:98-110](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-types.ts#L98-L110) |

Sources: [packages/next/src/lib/metadata/types/metadata-interface.ts:540-564](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L540-L564), [packages/next/src/lib/metadata/types/metadata-interface.ts:765-797](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts#L765-L797), [packages/next/src/lib/metadata/types/metadata-types.ts:38-110](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-types.ts#L38-L110)

## Hierarchical Metadata Resolution Pipeline

### Overview

The hierarchical metadata resolution pipeline processes layout-to-page tree accumulation, default fallback merging, and staged evaluation to generate final page-level configurations. The pipeline starts by initializing default baseline structures through dedicated factory helpers before traversing the component tree.

```typescript
export function createDefaultViewport(): ResolvedViewport {
  return {
    width: 'device-width',
    initialScale: 1,
    themeColor: null,
    colorScheme: null,
  }
}
```
Sources: [packages/next/src/lib/metadata/default-metadata.tsx:6-15](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/default-metadata.tsx#L6-L15)

The base metadata structure initializes properties such as title, description, application name, and various structured metadata objects to `null`, establishing a consistent fallback base for accumulation across nested layouts.
```typescript
export function createDefaultMetadata() {
  return {
    viewport: null,
    themeColor: null,
    colorScheme: null,
    metadataBase: null,
    title: null,
    description: null,
    applicationName: null,
    authors: null,
    generator: null,
    keywords: null,
    referrer: null,
    creator: null,
    publisher: null,
    robots: null,
    manifest: null,
    alternates: { canonical: null, languages: null, media: null, types: null },
    icons: null,
    openGraph: null,
    twitter: null,
    verification: {},
    appleWebApp: null,
    formatDetection: null,
    itunes: null,
    facebook: null,
    pinterest: null,
    abstract: null,
    appLinks: null,
    archives: null,
    assets: null,
    bookmarks: null,
    category: null,
    classification: null,
    pagination: { previous: null, next: null },
    other: {},
  }
}
```
Sources: [packages/next/src/lib/metadata/default-metadata.tsx:17-65](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/default-metadata.tsx#L17-L65)

### Default Fallback Merging and Static File Integration

Static file metadata resolution merges file-based fallback icons and manifest assets into target resolution objects. When static file metadata contains open graph or twitter images and current level metadata does not specify them, the pipeline resolves URLs and converts instances to string values.
```typescript
async function mergeStaticMetadata(
  metadataBase: MetadataBaseURL,
  source: Metadata | null,
  target: any,
  staticFilesMetadata: StaticMetadata,
  metadataContext: MetadataContext,
  titleTemplates: TitleTemplates,
  leafSegmentStaticIcons: StaticIcons,
  pathname: Promise<string>
) {
  if (!staticFilesMetadata) return target
  const { icon, apple, openGraph, twitter, manifest } = staticFilesMetadata

  if (icon) {
    leafSegmentStaticIcons.icon = icon
  }
  if (apple) {
    leafSegmentStaticIcons.apple = apple
  }

  if (twitter && !source?.twitter?.hasOwnProperty('images')) {
    const resolvedTwitter = resolveTwitter(
      { ...target.twitter, images: twitter } as Twitter,
      metadataBase,
      { ...metadataContext, isStaticMetadataRouteFile: true },
      titleTemplates.twitter
    )
    target.twitter = convertUrlsToStrings(resolvedTwitter)
  }

  if (openGraph && !source?.openGraph?.hasOwnProperty('images')) {
    const resolvedOpenGraph = await resolveOpenGraph(
      { ...target.openGraph, images: openGraph } as OpenGraph,
      metadataBase,
      pathname,
      { ...metadataContext, isStaticMetadataRouteFile: true },
      titleTemplates.openGraph
    )
    target.openGraph = convertUrlsToStrings(resolvedOpenGraph)
  }
  if (manifest) {
    target.manifest = manifest
  }

  return target
}
```
Sources: [packages/next/src/lib/metadata/resolve-metadata.ts:159-208](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts#L159-L208)

> [!NOTE]
> `mergeStaticMetadata` updates the leaf segment static icons directly on the reference object while conditionally resolving missing image arrays for Twitter and OpenGraph contexts using static route file flags.
> Sources: [packages/next/src/lib/metadata/resolve-metadata.ts:172-202](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts#L172-L202)

### Staged Evaluation and React Element Rendering

Functions like `getResolvedMetadataImpl` and `getNotFoundMetadataImpl` channel requests into async render passes that generate markup nodes.
```typescript
async function getResolvedMetadataImpl(
  tree: LoaderTree,
  pathname: Promise<string>,
  searchParams: Promise<ParsedUrlQuery>,
  interpolatedParams: Params,
  metadataContext: MetadataContext,
  isRuntimePrefetchable: boolean,
  errorType?: MetadataErrorType | 'redirect'
): Promise<React.ReactNode> {
  const errorConvention = errorType === 'redirect' ? undefined : errorType
  return renderMetadata(
    tree,
    pathname,
    searchParams,
    interpolatedParams,
    metadataContext,
    isRuntimePrefetchable,
    errorConvention
  )
}
```
Sources: [packages/next/src/lib/metadata/metadata.tsx:235-254](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L235-L254)

> [!WARNING]
> Viewport elements render a character set declaration followed by computed viewport attributes, color schemes, and media-query-bound theme colors, enforcing rigid ordering for mobile scaling parameters.
> Sources: [packages/next/src/lib/metadata/metadata.tsx:354-418](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L354-L418)

## Specialized Resolvers and URL Handling

### Overview

The metadata resolution subsystem transforms raw user-supplied metadata configurations into fully normalized, absolute URL-resolved output structures. This pipeline handles platform-specific social tags, title hierarchies, icons, and base URL resolution rules across different deployment environments.
Sources: [packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts:161-207](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts#L161-L207), [packages/next/src/lib/metadata/resolvers/resolve-url.ts:35-55](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-url.ts#L35-L55)

### OpenGraph and Twitter Card Resolution

Social metadata properties are processed by `resolveOpenGraph` and `resolveTwitter`, which inspect explicit object fields, validate images, and apply type-specific property constraints. OpenGraph property extraction relies on type definitions mapping types like `article`, `book`, `music.song`, and `video.movie` to their valid metadata fields.
Sources: [packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts:145-189](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts#L145-L189)

Twitter card resolution inspects the card type and configures subordinate structures such as `players` for the `player` card or `app` descriptors for the `app` card, defaulting to `summary_large_image` or `summary` depending on whether images are present.
Sources: [packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts:237-256](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts#L237-L256)

### Title Templates and Metadata Base URLs

Titles are resolved using `resolveTitle`, which evaluates string inputs against stashed template strings or processes object structures containing `default`, `absolute`, and `template` properties. When a template is active, `%s` placeholders are replaced with the target title value.
Sources: [packages/next/src/lib/metadata/resolvers/resolve-title.ts:4-40](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-title.ts#L4-L40)

URL resolution handles relative paths, absolute URLs, and environment-based fallbacks. `getSocialImageMetadataBaseFallback` inspects execution environments to determine appropriate base URLs:
Sources: [packages/next/src/lib/metadata/resolvers/resolve-url.ts:35-55](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-url.ts#L35-L55)

| Environment / Condition | Fallback Target | Source Reference |
| :--- | :--- | :--- |
| Development (`NODE_ENV === 'development'`) | Localhost (`http://localhost:3000` or custom port) | [packages/next/src/lib/metadata/resolvers/resolve-url.ts:10-15](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-url.ts#L10-L15) |
| Vercel Preview (`NODE_ENV === 'production'` & `VERCEL_ENV === 'preview'`) | Preview Deployment URL (`VERCEL_BRANCH_URL` or `VERCEL_URL`) | [packages/next/src/lib/metadata/resolvers/resolve-url.ts:17-20](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-url.ts#L17-L20) |
| Production Default | User-provided `metadataBase` $\rightarrow$ Vercel Production URL (`VERCEL_PROJECT_PRODUCTION_URL`) $\rightarrow$ Localhost | [packages/next/src/lib/metadata/resolvers/resolve-url.ts:22-25](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-url.ts#L22-L25) |

> [!WARNING]
> When no explicit `metadataBase` is set for relative social images in production, Next.js falls back to localhost or Vercel environment variables and emits a warning if Vercel system environment variables are not exposed.
> Sources: [packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts:66-97](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts#L66-L97)

### Icon Normalization

Icons are resolved through `resolveIcons`, which processes input arrays, string URLs, or structured icon descriptor objects containing keys defined in `IconKeys`, ensuring `icon` and `apple` attachment fields are properly structured arrays.
Sources: [packages/next/src/lib/metadata/resolvers/resolve-icons.ts:14-34](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-icons.ts#L14-L34)

```typescript
export const resolveIcons: FieldResolver<'icons'> = (icons) => {
  if (!icons) {
    return null
  }

  const resolved: any = {
    icon: [],
    apple: [],
  }
  if (Array.isArray(icons)) {
    resolved.icon = icons.map(resolveIcon).filter(Boolean)
  } else if (isStringOrURL(icons)) {
    resolved.icon = [resolveIcon(icons)]
  } else {
    for (const key of IconKeys) {
      const values = resolveAsArrayOrUndefined(icons[key])
      if (values) resolved[key] = values.map(resolveIcon)
    }
  }
  return resolved
}
```
Sources: [packages/next/src/lib/metadata/resolvers/resolve-icons.ts:14-34](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/resolvers/resolve-icons.ts#L14-L34)

## Metadata Route Identification and Routing

### Overview

Special metadata files such as `robots.txt`, `sitemap.xml`, `manifest.json`, `manifest.webmanifest`, favicons, and social images require specialized detection, extension mapping, and routing classification. Next.js identifies these files using pre-compiled regular expressions and fast-path heuristics, normalizing them into application routes or static metadata routes.
Sources: [packages/next/src/lib/metadata/is-metadata-route.ts:6-162](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L6-L162), [packages/next/src/lib/metadata/get-metadata-route.ts:164-196](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L164-L196)

### Static Image Definitions and File Extensions

The framework maintains explicit extension boundaries for static metadata image categories (`icon`, `apple`, `favicon`, `openGraph`, `twitter`) and metadata route extensions.
Sources: [packages/next/src/lib/metadata/is-metadata-route.ts:6-31](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L6-L31)

| Metadata Category | Filename Base | Supported Extensions | Sources Reference |
| :--- | :--- | :--- | :--- |
| `icon` | `icon` | `ico`, `jpg`, `jpeg`, `png`, `svg` | [packages/next/src/lib/metadata/is-metadata-route.ts:6-10](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L6-L10) |
| `apple` | `apple-icon` | `jpg`, `jpeg`, `png` | [packages/next/src/lib/metadata/is-metadata-route.ts:11-14](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L11-L14) |
| `favicon` | `favicon` | `ico` | [packages/next/src/lib/metadata/is-metadata-route.ts:15-18](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L15-L18) |
| `openGraph` | `opengraph-image` | `jpg`, `jpeg`, `png`, `gif` | [packages/next/src/lib/metadata/is-metadata-route.ts:19-22](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L19-L22) |
| `twitter` | `twitter-image` | `jpg`, `jpeg`, `png`, `gif` | [packages/next/src/lib/metadata/is-metadata-route.ts:23-26](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L23-L26) |

Sources: [packages/next/src/lib/metadata/is-metadata-route.ts:6-27](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L6-L27)

### Fast-Path Matching and Compiled Regexes

Path checking optimizes file routing via `fastPathCheck`, which evaluates exact path matches for `favicon.ico`, `robots.txt`, `manifest.json`, `manifest.webmanifest`, and `sitemap.xml` before falling back to full compiled regular expressions.
Sources: [packages/next/src/lib/metadata/is-metadata-route.ts:60-95](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L60-L95)

```typescript
const FAVICON_REGEX = /^[\\/]favicon\.ico$/
const ROBOTS_TXT_REGEX = /^[\\/]robots\.txt$/
const MANIFEST_JSON_REGEX = /^[\\/]manifest\.json$/
const MANIFEST_WEBMANIFEST_REGEX = /^[\\/]manifest\.webmanifest$/
const SITEMAP_XML_REGEX = /[\\/]sitemap\.xml$/

function fastPathCheck(normalizedPath: string): boolean | null {
  if (FAVICON_REGEX.test(normalizedPath)) return true
  if (ROBOTS_TXT_REGEX.test(normalizedPath)) return true
  if (MANIFEST_JSON_REGEX.test(normalizedPath)) return true
  if (MANIFEST_WEBMANIFEST_REGEX.test(normalizedPath)) return true
  if (SITEMAP_XML_REGEX.test(normalizedPath)) return true

  if (
    !normalizedPath.includes('robots') &&
    !normalizedPath.includes('manifest') &&
    !normalizedPath.includes('sitemap') &&
    !normalizedPath.includes('icon') &&
    !normalizedPath.includes('apple-icon') &&
    !normalizedPath.includes('opengraph-image') &&
    !normalizedPath.includes('twitter-image') &&
    !normalizedPath.includes('favicon')
  ) {
    return false
  }

  return null
}
```
Sources: [packages/next/src/lib/metadata/is-metadata-route.ts:60-95](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L60-L95)

> [!NOTE]
> If a path fails exact fast-path matching and contains no metadata keywords, `fastPathCheck` immediately returns `false` to bypass regex compilation and testing.
> Sources: [packages/next/src/lib/metadata/is-metadata-route.ts:80-95](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/is-metadata-route.ts#L80-L95)

### Route Normalization and Filesystem getItem Pipeline

Metadata routes are normalized via `normalizeMetadataRoute`, mapping static file pages and dynamic route pages into their corresponding `/route` filesystem structures.
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:171-196](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L171-L196)

```typescript
export function normalizeMetadataRoute(page: string) {
  if (!isMetadataPage(page)) {
    return page
  }
  let route = page
  let suffix = ''
  if (page === '/robots') {
    route += '.txt'
  } else if (page === '/manifest') {
    route += '.webmanifest'
  } else {
    suffix = getMetadataRouteSuffix(page)
  }
  if (!route.endsWith('/route')) {
    const { dir, name: baseName, ext } = path.parse(route)
    route = path.posix.join(
      dir,
      `${baseName}${suffix ? `-${suffix}` : ''}${ext}`,
      'route'
    )
  }

  return route
}
```
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:171-196](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L171-L196)

During runtime filesystem routing in development (`opts.dev`), `getItem` intercepts metadata route files via `isMetadataRouteFile` and resolves them through `staticMetadataFiles`.
Sources: [packages/next/src/server/lib/router-utils/filesystem.ts:515-525](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/filesystem.ts#L515-L525)

```typescript
      if (opts.dev && isMetadataRouteFile(itemPath, [], false)) {
        const fsPath = staticMetadataFiles.get(itemPath)
        if (fsPath) {
          return {
            type: 'nextStaticFolder',
            fsPath,
            itemPath: fsPath,
          }
        }
      }
```
Sources: [packages/next/src/server/lib/router-utils/filesystem.ts:515-525](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/filesystem.ts#L515-L525)

> [!WARNING]
> Sitemaps are explicitly excluded from suffix generation (`getMetadataRouteSuffix`) because each sitemap aggregates URLs across sub-routes, ensuring userland contains exactly one sitemap per pathname.
> Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:24-39](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L24-L39)

## Metadata Route Segment Normalization

### Overview

Metadata route segments undergo normalization to handle group routes, parallel route folders, and dynamic parameters when generating physical route filenames and matching regular expressions. When a metadata file resides within a nested directory structure containing route groups `(group)` or parallel route slots `@slot`, a unique hash suffix is appended to prevent filename collisions. Sitemaps are excluded from this hashing behavior because they aggregate sub-route URLs and require a single canonical pathname. Route segment normalization also translates dynamic parameters into placeholder patterns for static prerendering and compiles named route regular expressions for runtime matching.
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:31-52](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L52), [packages/next/src/shared/lib/router/utils/route-regex.ts:378-403](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L378-L403)

### Call-Chain Execution Walkthroughs

Execution flows through explicit functional chains when handling metadata segment interpolation, route suffix resolution, and named regex compilation.

1. **Static Segment Normalization Chain (`fillMetadataSegment` → `fillStaticMetadataSegment` → `getStaticMetadataRoute` → `normalizeStaticMetadataRouteSegment`)**: `fillMetadataSegment` evaluates whether the segment is static or dynamic, delegating to `fillStaticMetadataSegment` [packages/next/src/lib/metadata/get-metadata-route.ts:141-160](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L141-L160), which resolves the base path using `getStaticMetadataRoute` [packages/next/src/lib/metadata/get-metadata-route.ts:90-101](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L90-L101) and cleans each segment through `normalizeStaticMetadataRouteSegment` [packages/next/src/lib/metadata/get-metadata-route.ts:62-73](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L62-73).

```typescript
export function fillStaticMetadataSegment(
  segment: string,
  lastSegment: string
) {
  return normalizePathSep(
    path.join(
      getStaticMetadataRoute(segment),
      getMetadataRouteFilename(segment, lastSegment)
    )
  )
}
```
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:90-101](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L90-L101)

2. **Route Suffix and Slot Detection Chain (`fillMetadataSegment` → `fillStaticMetadataSegment` → `getMetadataRouteFilename` → `getMetadataRouteSuffix` → `isParallelRouteSegment`)**: `fillMetadataSegment` calls `fillStaticMetadataSegment` [packages/next/src/lib/metadata/get-metadata-route.ts:141-160](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L141-L160), invoking `getMetadataRouteFilename` [packages/next/src/lib/metadata/get-metadata-route.ts:53-61](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L53-61), which calculates the route suffix via `getMetadataRouteSuffix` [packages/next/src/lib/metadata/get-metadata-route.ts:31-52](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L52) and checks segment properties using `isParallelRouteSegment` [packages/next/src/shared/lib/segment.ts:11-14](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/segment.ts#L11-L14).

```typescript
function getMetadataRouteSuffix(page: string) {
  const parentPathname = path.dirname(page)
  if (page.endsWith('/sitemap') || page.endsWith('/sitemap.xml')) {
    return ''
  }
  let suffix = ''
  const segments = parentPathname.split('/')
  if (
    segments.some((seg) => isGroupSegment(seg) || isParallelRouteSegment(seg))
  ) {
    suffix = djb2Hash(parentPathname).toString(36).slice(0, 6)
  }
  return suffix
}
```
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:31-52](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L52)

3. **Named Route Regex Compilation Chain**: `fillMetadataSegment` generates named regular expressions by calling `getNamedRouteRegex`, which delegates to `getNamedParametrizedRoute` and constructs minimal route keys via `buildGetSafeRouteKey`.
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:141-160](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L141-L160), [packages/next/src/shared/lib/router/utils/route-regex.ts:177-190](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-190), [packages/next/src/shared/lib/router/utils/route-regex.ts:378-403](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L378-L403)

```typescript
function buildGetSafeRouteKey() {
  let i = 0

  return () => {
    let routeKey = ''
    let j = ++i
    while (j > 0) {
      routeKey += String.fromCharCode(97 + ((j - 1) % 26))
      j = Math.floor((j - 1) / 26)
    }
    return routeKey
  }
}
```
Sources: [packages/next/src/shared/lib/router/utils/route-regex.ts:177-190](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-190)

```mermaid
sequenceDiagram
    participant getMetadataRoute as get-metadata-route.ts
    participant segmentModule as segment.ts
    participant routeRegexModule as route-regex.ts

    getMetadataRoute->>getMetadataRoute: fillMetadataSegment()
    getMetadataRoute->>getMetadataRoute: fillStaticMetadataSegment()
    getMetadataRoute->>getMetadataRoute: getStaticMetadataRoute()
    getMetadataRoute->>getMetadataRoute: normalizeStaticMetadataRouteSegment()
    getMetadataRoute->>getMetadataRoute: getMetadataRouteFilename()
    getMetadataRoute->>getMetadataRoute: getMetadataRouteSuffix()
    getMetadataRoute->>segmentModule: isParallelRouteSegment()
    getMetadataRoute->>routeRegexModule: getNamedRouteRegex()
    routeRegexModule->>routeRegexModule: getNamedParametrizedRoute()
    routeRegexModule->>routeRegexModule: buildGetSafeRouteKey()
```
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:31-160](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L160), [packages/next/src/shared/lib/router/utils/route-regex.ts:177-403](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-403), [packages/next/src/shared/lib/segment.ts:11-14](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/segment.ts#L11-L14)

### Route Segment Reference Tables

The normalization and regex compilation utilities rely on specific helper functions and helper patterns to parse routes.

| Utility Function | Module Path | Purpose | Sources Reference |
| :--- | :--- | :--- | :--- |
| `getMetadataRouteSuffix` | `src/lib/metadata/get-metadata-route.ts` | Computes a 6-character base36 hash suffix using `djb2Hash` for parent paths containing route groups or parallel route segments. | [packages/next/src/lib/metadata/get-metadata-route.ts:31-52](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L52) |
| `normalizeStaticMetadataRouteSegment` | `src/lib/metadata/get-metadata-route.ts` | Iteratively replaces parameter patterns in static segments with `-` placeholders. | [packages/next/src/lib/metadata/get-metadata-route.ts:62-73](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L62-L73) |
| `buildGetSafeRouteKey` | `src/shared/lib/router/utils/route-regex.ts` | Generates minimal lowercase alphabetical route keys (`a`, `b`, ..., `z`, `aa`) for regex named groups. | [packages/next/src/shared/lib/router/utils/route-regex.ts:177-190](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-L190) |
| `isGroupSegment` | `src/shared/lib/segment.ts` | Identifies route group folders enclosed in parentheses, such as `(post)`. | [packages/next/src/shared/lib/segment.ts:7-10](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/segment.ts#L7-L10) |
| `isParallelRouteSegment` | `src/shared/lib/segment.ts` | Detects parallel route slots starting with `@` excluding `@children`. | [packages/next/src/shared/lib/segment.ts:11-14](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/segment.ts#L11-L14) |

Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:31-73](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L73), [packages/next/src/shared/lib/router/utils/route-regex.ts:177-190](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-190), [packages/next/src/shared/lib/segment.ts:7-14](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/segment.ts#L7-L14)

> [!NOTE]
> When `getMetadataRouteSuffix` evaluates a path, it checks if any segment in the parent path pathname satisfies `isGroupSegment` or `isParallelRouteSegment`. If true, it computes `djb2Hash(parentPathname).toString(36).slice(0, 6)`.
> Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:43-50](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L43-L50)

### Design Trade-Offs

| Design Choice | Benefit | Cost | Sources Reference |
| :--- | :--- | :--- | :--- |
| **Hash-based Suffix for Group/Parallel Paths** | Prevents filename collisions when multiple metadata files map to the same output directory due to route grouping or parallel slots. | Adds non-deterministic or obscured hash suffixes (`-[0-9a-z]{6}`) to generated static asset filenames. | [packages/next/src/lib/metadata/get-metadata-route.ts:31-52](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L31-L52) |
| **Sitemap Suffix Exclusion** | Ensures sitemaps aggregate sub-routes correctly without generating separate fragmented files per route group. | Risks path collisions if multiple userland sitemaps share an identical output pathname without proper separation. | [packages/next/src/lib/metadata/get-metadata-route.ts:24-39](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L24-L39) |
| **Safe Route Key Generation (`a-z`)** | Keeps compiled named regex groups compact and avoids invalid JavaScript/RegExp identifier characters. | Requires internal translation tables (`routeKeys` and `reference.names`) to map back to original parameter keys. | [packages/next/src/shared/lib/router/utils/route-regex.ts:177-190](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-L190) |

Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:24-52](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L24-L52), [packages/next/src/shared/lib/router/utils/route-regex.ts:177-190](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L177-L190)

> [!WARNING]
> Invalid parameter keys (such as keys exceeding 30 characters or starting with a number) automatically fallback to `getSafeRouteKey()` during `getSafeKeyFromSegment` execution to preserve regular expression validity.
> Sources: [packages/next/src/shared/lib/router/utils/route-regex.ts:220-229](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L220-L229)

### Worked Example: Segment Filling and Route Regex Generation

The following example demonstrates how `fillMetadataSegment` processes a dynamic route path versus a static metadata prerender path using the underlying signature functions.

```typescript
import { fillMetadataSegment, getStaticMetadataPrerenderPathname } from './get-metadata-route'
import { getNamedRouteRegex } from '../../shared/lib/router/utils/route-regex'

// 1. Dynamic metadata segment filling with provided parameters
const dynamicFilled = fillMetadataSegment(
  '/a/[slug]',
  { slug: 'b' },
  'open-graph',
  false
)
// Result: '/a/b/open-graph'

// 2. Static metadata prerender path conversion (replaces dynamic segments with '-')
const staticPrerender = getStaticMetadataPrerenderPathname('/a/[slug]/opengraph-image.tsx')
// Result: '/a/-/opengraph-image-[hash]' or similar normalized path

// 3. Compiling a named route regex with route keys
const routeRegexResult = getNamedRouteRegex('/a/[slug]', {
  prefixRouteKeys: false,
  includeSuffix: false,
  includePrefix: false,
})
// Yields namedRegex '^/a/(?<slug>[^/]+?)(?:/)?$' and routeKeys { slug: 'slug' }
```
Sources: [packages/next/src/lib/metadata/get-metadata-route.ts:107-160](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/get-metadata-route.ts#L107-L160), [packages/next/src/shared/lib/router/utils/route-regex.ts:378-403](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/route-regex.ts#L378-L403)

## RSC Rendering and Export Integration

### Overview

The conversion of resolved metadata into React server head elements and its integration with static export serialization relies on structured component creation and file writing utilities. The `createMetadataComponents` function generates three primary React components: `Viewport`, `Metadata`, and `MetadataOutlet`. These components orchestrate the rendering sequence for head tags, utilizing internal resolution pipelines like `resolveMetadata` and `resolveViewport`.
Sources: [packages/next/src/lib/metadata/metadata.tsx:41-63](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L41-L63), [packages/next/src/lib/metadata/metadata.tsx:234-293](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L234-L293)

### Element Creation and Static Export Serialization

The conversion process transitions resolved interface objects into native React element nodes through `createViewportElements` and `createMetadataElements`. For viewports, tags such as `meta[charset="utf-8"]`, viewport string interpolations, `theme-color`, and `color-scheme` are systematically appended to a tag array. For metadata, properties like title and description generate corresponding HTML elements. During static export operations handled by `app-page.ts`, the resulting page metadata, status, headers, and segment paths are compiled into a `RouteMetadata` structure and written out via `fileWriter.append()` with `NEXT_META_SUFFIX`.
Sources: [packages/next/src/lib/metadata/metadata.tsx:354-448](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L354-L448), [packages/next/src/export/routes/app-page.ts:216-228](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-page.ts#L216-L228)

> [!WARNING]
> If a client-side rendering bailout or dynamic usage error occurs during static export generation, rendering fails unless trapped by specific error conventions like `isDynamicUsageError` or `isBailoutToCSRError`.
> Sources: [packages/next/src/export/routes/app-page.ts:250-260](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-page.ts#L250-L260)

### Call-Chain Execution Walkthrough

The metadata rendering pipeline executes in a structured sequence from asynchronous resolution to React node serialization:
1. `getResolvedMetadataImpl()` or `getNotFoundMetadataImpl()` receives the loader tree, pathname, search params, and context.
2. It invokes `renderMetadata()`.
3. `renderMetadata()` calls `resolveMetadata()`.
4. The resulting processed metadata object is passed into `createMetadataElements()`, returning a fragment of React elements.
Sources: [packages/next/src/lib/metadata/metadata.tsx:235-331](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L235-L331)

### Metadata Rendering Components Reference

| Component / Function | Input Parameters | Return Value | Sources Reference |
| :--- | :--- | :--- | :--- |
| `createMetadataComponents` | `tree`, `pathname`, `parsedQuery`, `metadataContext`, `interpolatedParams`, `errorType`, `serveStreamingMetadata`, `isRuntimePrefetchable` | `{ Viewport, Metadata, MetadataOutlet }` | [packages/next/src/lib/metadata/metadata.tsx:41-63](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L41-L63) |
| `createViewportElements` | `viewport: ResolvedViewport` | `React.ReactElement[]` | [packages/next/src/lib/metadata/metadata.tsx:354-356](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L354-L356) |
| `createMetadataElements` | `metadata: any` | `React.ReactElement[]` | [packages/next/src/lib/metadata/metadata.tsx:426-428](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L426-L428) |
| `createMetadataContext` | `renderOpts` | `MetadataContext` | [packages/next/src/lib/metadata/metadata-context.tsx:4-11](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata-context.tsx#L4-L11) |

Sources: [packages/next/src/lib/metadata/metadata.tsx:41-63](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L41-L63), [packages/next/src/lib/metadata/metadata.tsx:354-356](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L354-L356), [packages/next/src/lib/metadata/metadata.tsx:426-428](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx#L426-L428), [packages/next/src/lib/metadata/metadata-context.tsx:4-11](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/metadata/metadata-context.tsx#L4-L11)

## Related

- [App Server Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/app-server-rendering)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
