Root layout with metadata in Next.js 13
In app/layout.tsx, export a metadata object with type Metadata imported from 'next': `export const metadata: Metadata = { title: 'Home', description: 'Welcome to Next.js' }`. In JavaScript, export a plain metadata object without the type annotation.
Replace next/head with metadata API
In pages directory, next/head is used to manage head HTML elements. In app directory, next/head is replaced with the built-in SEO support using metadata. Export a metadata object or use generateMetadata function instead of using the Head component.
Metadata APIs available in Next.js
Three metadata APIs are available: (1) the static metadata object, (2) the dynamic generateMetadata function, and (3) special file conventions for static or dynamically generated favicons and OG images. Both metadata object and generateMetadata function exports are only supported in Server Components.
Default meta tags always added
Two default meta tags are always added even if a route doesn't define metadata: a meta charset tag setting character encoding to utf-8, and a meta viewport tag setting content to width=device-width, initial-scale=1.
Static metadata export location
Static metadata is defined by exporting a Metadata object from a static layout.js or page.js file. The metadata object must be exported as a named export.
generateMetadata function signature
The generateMetadata function accepts two parameters: (1) an object containing params (a Promise of route parameters) and searchParams (a Promise of query string parameters), and (2) parent, a ResolvingMetadata instance. It returns a Promise<Metadata>.
Streaming metadata behavior
For dynamically rendered pages, Next.js streams metadata separately, injecting it into the HTML once generateMetadata resolves, without blocking UI rendering. This improves perceived performance by allowing visual content to stream first. However, streaming metadata is disabled for bots and crawlers that expect metadata in the head tag (detected via User Agent header), such as Twitterbot, Slackbot, and Bingbot. Prerendered pages do not use streaming since metadata is resolved at build time.
File-based metadata special files
The following special files are available for metadata: favicon.ico, apple-icon.jpg, and icon.jpg for favicons; opengraph-image.jpg and twitter-image.jpg for OG images; robots.txt for search engine directives; sitemap.xml for sitemaps. These can be used as static files or generated programmatically.
Favicon placement
Add a favicon.ico file to the root of the app folder to add a favicon to the application.
Static Open Graph image placement
Create an opengraph-image.jpg file in the root of the app folder to add a static OG image. OG images can also be added for specific routes by creating opengraph-image.jpg deeper in the folder structure. More specific images in the folder hierarchy take precedence over OG images higher up.
OG image file formats supported
OG image file formats supported include jpeg, jpg, png, and gif in addition to the standard opengraph-image.jpg naming convention.
ImageResponse constructor for dynamic OG images
The ImageResponse constructor from next/og allows generating dynamic images using JSX and CSS. It is useful for OG images that depend on data. The constructor returns a PNG image that can be used as an opengraph-image.tsx or similar file.
ImageResponse required exports
When generating OG images with ImageResponse, export a size object with width and height properties, export a contentType string (typically 'image/png'), and export a default async function that returns new ImageResponse().
ImageResponse JSX and CSS support
ImageResponse supports common CSS properties including flexbox and absolute positioning, custom fonts, text wrapping, centering, and nested images. Only flexbox and a subset of CSS properties are supported; advanced layouts like display: grid will not work.
ImageResponse implementation details
ImageResponse uses @vercel/og, satori, and resvg to convert HTML and CSS into PNG.
Dynamic OG image example with ImageResponse
Example showing how to generate dynamic OG images for blog posts:
```tsx
import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/data'
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
export default async function Image({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = await getPost(slug)
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{post.title}
</div>
)
)
}
```
This example shows a route-specific OG image generator in app/blog/[slug]/opengraph-image.tsx that fetches post data and renders it as an image.
generateMetadata function with dynamic params example
Example of using generateMetadata to fetch and return metadata for a specific blog post:
```tsx
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: Promise<{ slug: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const slug = (await params).slug
const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
res.json()
)
return {
title: post.title,
description: post.description,
}
}
export default function Page({ params, searchParams }: Props) {}
```
The function receives route parameters as a Promise and must await them before use.
Static metadata export example
Example of defining static metadata by exporting a metadata object from a layout file:
```tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Blog',
description: '...',
}
export default function Layout() {}
```
The metadata object is exported as a named export in layout.js or page.js files.
htmlLimitedBots configuration option
The htmlLimitedBots option in next.config.js can be used to customize or disable streaming metadata for bots and crawlers.