# How to Manage Custom Meta Tags with Nextjs Metadata Static Objects

> Learn to manage custom meta tags in Nextjs 13 using static metadata objects. Discover best practices for adding arbitrary and dynamic meta tags effectively.

- Repository: [Vercel/next.js](https://github.com/vercel/next.js)
- Tags: best-practices
- Published: 2026-02-16

---

**Export a typed `Metadata` object from any layout or page file and use the `other` field to inject arbitrary `<meta>` tags, or use `generateMetadata` for dynamic data, ensuring you set `metadataBase` for absolute URLs.**

Next.js 13+ App Router revolutionizes `<head>` management through **static metadata objects** and the `generateMetadata` function. These APIs are processed by the framework's internal resolver to produce SEO-friendly tags without manual DOM manipulation. Understanding how Nextjs metadata flows from source files to HTML elements ensures type-safe, maintainable SEO configurations.

## How Nextjs Metadata Resolution Works

The framework processes metadata through a four-stage pipeline defined in [`packages/next/src/lib/metadata/resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts). First, it **collects** exports from every layout and page segment in the route tree. Then it **resolves** each export—whether a static object or `generateMetadata` function—into a `ResolvedMetadata` shape. Next, it **merges** these objects using `accumulateMetadata`, applying defaults from [`packages/next/src/lib/metadata/default-metadata.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/default-metadata.tsx), handling `metadataBase` normalization, and deduplicating icons. Finally, [`packages/next/src/lib/metadata/metadata.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/metadata.tsx) invokes `createMetadataElements` to render the React components that output actual `<meta>` and `<link>` tags.

Key implementation files include:

- **[`packages/next/src/lib/metadata/types/metadata-interface.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/types/metadata-interface.ts)** – Defines the `Metadata` type interface including the `other` catch-all field.
- **[`packages/next/src/lib/metadata/resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts)** – Contains the core `resolveMetadata` function and `normalizeMetadataBase` logic (lines 48-56).
- **[`packages/next/src/lib/metadata/generate/basic.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/generate/basic.tsx)** – Renders basic meta tags via `BasicMeta` and processes the `other` field (lines 119-130).

## Declaring Static Metadata Objects

The simplest approach exports a typed object from [`layout.tsx`](https://github.com/vercel/next.js/blob/main/layout.tsx) or [`page.tsx`](https://github.com/vercel/next.js/blob/main/page.tsx). This method is fully typed when you import `Metadata` from `'next'`.

### Using the `other` Field for Custom Meta Tags

For non-standard meta tags, use the `other` field, which accepts a record of string keys mapping to string, number, or array values. According to [`metadata-interface.ts`](https://github.com/vercel/next.js/blob/main/metadata-interface.ts), this field is typed as `{ [name: string]: string | number | (string | number)[] }`.

```tsx
// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: 'My Application',
    template: '%s | My Application',
  },
  description: 'A modern Next.js application',
  other: {
    author: 'Development Team',
    'theme-color': '#000000',
    keywords: ['nextjs', 'react', 'typescript'],
  },
}

```

The framework renders these as `<meta name="author" content="Development Team">` and handles arrays by creating multiple elements for the same name. The `BasicMeta` component in [`basic.tsx`](https://github.com/vercel/next.js/blob/main/basic.tsx) (lines 119-130) iterates over this map to generate the final HTML.

### Configuring `metadataBase` for Absolute URLs

When referencing relative paths for Open Graph images or canonical URLs, define `metadataBase` to ensure crawlers receive fully qualified URLs. The resolver normalizes this base via `normalizeMetadataBase` in [`resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/resolve-metadata.ts) (lines 48-56).

```tsx
export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  openGraph: {
    images: [{ url: '/og-image.png' }], // Resolves to https://example.com/og-image.png
  },
}

```

## Generating Dynamic Meta Tags

For route-dependent metadata, export an async `generateMetadata` function instead of a static object. Next.js calls this function during server-side rendering, passing the same `params` and `searchParams` received by the page component.

```tsx
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

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

  return {
    title: post.title,
    description: post.excerpt,
    other: {
      'article:published_time': post.publishedAt,
      'article:author': post.authorName,
    },
  }
}

```

During the merge phase, the framework combines this generated object with parent layout metadata, preserving title template inheritance (handled in [`resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/resolve-metadata.ts) lines 77-84). Do not export both `metadata` and `generateMetadata` from the same file, as the system warns against and ignores one export.

## Nextjs Metadata Best Practices

Follow these implementation guidelines to avoid common pitfalls:

- **Type your exports**. Always import `Metadata` from `'next'` to enable IntelliSense and catch deprecated fields at compile time.
- **Use `other` for arbitrary tags**. Avoid manually rendering `<head>` elements; the `other` field guarantees proper escaping and rendering order via `OtherMeta` in the generation pipeline.
- **Separate viewport configuration**. Move `themeColor`, `colorScheme`, and `viewport` properties to a dedicated `export const viewport` object. Placing them in `metadata` triggers warnings (see [`resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/resolve-metadata.ts) lines 21-24) and will be removed in future versions.
- **Set `metadataBase` globally**. Define this in your root layout to ensure all relative Open Graph and Twitter image URLs resolve correctly across environments.
- **Leverage title templates**. Use the `{ default: string; template: string }` object to let child routes inject their titles while maintaining brand consistency (e.g., `"Post Title | Site Name"`).
- **Prioritize icon placement**. Place favicon files in the deepest layout segment; the resolver uses `leafSegmentStaticIcons` to ensure the correct `<link rel="icon">` appears first.
- **Minimize blocking data fetches**. Only `await` data essential for SEO in `generateMetadata` to prevent "metadata fetch timeout" errors and degraded TTFB.

## Full-Stack Implementation Example

This example demonstrates a root layout with static global metadata and a dynamic page extending it:

```tsx
// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://demo.vercel.app'),
  title: { default: 'Demo Site', template: '%s – Demo Site' },
  description: 'Production-ready Next.js metadata implementation',
  alternates: { canonical: '/' },
  other: {
    'msapplication-TileColor': '#da532c',
    'x-custom-platform': 'web',
    keywords: ['nextjs', 'app-router', 'metadata'],
  },
}

```

```tsx
// app/products/[id]/page.tsx
import type { Metadata } from 'next'

export async function generateMetadata({
  params,
}: {
  params: { id: string }
}): Promise<Metadata> {
  const product = await getProduct(params.id)

  return {
    title: product.name,
    description: product.summary,
    openGraph: {
      title: product.name,
      images: [{ url: `/og/products/${params.id}.png` }],
    },
    other: {
      'product:price:amount': product.price,
      'product:price:currency': 'USD',
    },
  }
}

```

The resulting HTML includes:

```html
<title>Wireless Headphones – Demo Site</title>
<meta name="description" content="Noise-cancelling over-ear headphones">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="x-custom-platform" content="web">
<meta name="keywords" content="nextjs,app-router,metadata">
<meta property="og:title" content="Wireless Headphones">
<meta property="og:image" content="https://demo.vercel.app/og/products/123.png">
<meta name="product:price:amount" content="299.00">

```

## Summary

- Export a typed `Metadata` object from layouts or pages to define static tags, using the `other` field for custom meta properties.
- Implement `generateMetadata` for dynamic, data-driven tags that merge automatically with parent metadata.
- Always set `metadataBase` in your root layout when using relative URLs for Open Graph images or canonical links.
- Move viewport-related settings (`themeColor`, `viewport`) to a separate `viewport` export to avoid deprecation warnings.
- Reference [`packages/next/src/lib/metadata/resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/resolve-metadata.ts) for the merge logic and [`packages/next/src/lib/metadata/generate/basic.tsx`](https://github.com/vercel/next.js/blob/main/packages/next/src/lib/metadata/generate/basic.tsx) for the rendering implementation.

## Frequently Asked Questions

### What is the difference between `metadata` and `generateMetadata` in Next.js?

**Static `metadata` objects** are evaluated at build time for pages with fixed SEO requirements, while **`generateMetadata`** is an async function called during rendering for dynamic routes. According to the source code in [`resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/resolve-metadata.ts), the system treats both exports identically during the resolution phase, but you cannot use both in the same file.

### How do I add multiple values for the same meta tag name?

Pass an array to the `other` field in your metadata object. The type definition in [`metadata-interface.ts`](https://github.com/vercel/next.js/blob/main/metadata-interface.ts) allows `string | number | (string | number)[]`, and the `BasicMeta` generator in [`basic.tsx`](https://github.com/vercel/next.js/blob/main/basic.tsx) iterates over arrays to create multiple `<meta>` elements with the same name attribute but different content values.

### Why are my Open Graph images showing as relative URLs?

You likely omitted `metadataBase`. The resolver in [`resolve-metadata.ts`](https://github.com/vercel/next.js/blob/main/resolve-metadata.ts) (lines 48-56) uses this property to prefix relative paths. Without it, Next.js cannot generate absolute URLs required by social media crawlers, resulting in broken link previews.

### Can I use both `metadata` and `generateMetadata` in the same route segment?

No. The Next.js metadata resolution system explicitly warns against and ignores one export if both are present. Choose the static object for hardcoded values or `generateMetadata` when you need to fetch data, as implemented in the `accumulateMetadata` function.