Metadata and OG images
Last updated April 23, 2026
The Metadata APIs can be used to define your application metadata for improved SEO and web shareability and include:
- The static
metadataobject - The dynamic
generateMetadatafunction - Special file conventions that can be used to add static or dynamically generated favicons and OG images.
With all the options above, Next.js will automatically generate the relevant <head> tags for your page, which can be inspected in the browser's developer tools.
The metadata object and generateMetadata function exports are only supported in Server Components.
Default fields
There are two default meta tags that are always added even if a route doesn't define metadata:
- The meta charset tag sets the character encoding for the website.
- The meta viewport tag sets the viewport width and scale for the website to adjust for different devices.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
The other metadata fields can be defined with the Metadata object (for static metadata) or the generateMetadata function (for generated metadata).
Static metadata
To define static metadata, export a Metadata object from a static layout.js or page.js file. For example, to add a title and description to the blog route:
app/blog/layout.tsx
JavaScriptTypeScript
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Blog',
description: '...',
}
export default function Layout() {}
You can view a full list of available options, in the generateMetadata documentation.
Generated metadata
You can use generateMetadata function to fetch metadata that depends on data. For example, to fetch the title and description for a specific blog post:
app/blog/[slug]/page.tsx
JavaScriptTypeScript
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
// fetch post information
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) {}
Streaming metadata
For dynamically rendered pages, Next.js streams metadata separately, injecting it into the HTML once generateMetadata resolves, without blocking UI rendering.
Streaming metadata improves perceived performance by allowing visual content to stream first.
Streaming metadata is disabled for bots and crawlers that expect metadata to be in the <head> tag (e.g. Twitterbot, Slackbot, Bingbot). These are detected by using the User Agent header from the incoming request.
You can customize or disable streaming metadata completely, with the htmlLimitedBots option in your Next.js config file.
Prerendered pages don’t use streaming since metadata is resolved at build time.
Learn more about streaming metadata.
Memoizing data requests
There may be cases where you need to fetch the same data for metadata and the page itself. To avoid duplicate requests, you can use React's cache function to memoize the return value and only fetch the data once. For example, to fetch the blog post information for both the metadata and the page:
app/lib/data.ts
JavaScriptTypeScript
import { cache } from 'react'
import { db } from '@/app/lib/db'
// getPost will be used twice, but execute only once
export const getPost = cache(async (slug: string) => {
const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
return res
})
app/blog/[slug]/page.tsx
JavaScriptTypeScript
import { getPost } from '@/app/lib/data'
export async function generateMetadata({
params,
}: {
params: { slug: string }
}) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.description,
}
}
export default async function Page({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return <div>{post.title}</div>
}
File-based metadata
The following special files are available for metadata:
- favicon.ico, apple-icon.jpg, and icon.jpg
- opengraph-image.jpg and twitter-image.jpg
- robots.txt
- sitemap.xml
You can use these for static metadata, or you can programmatically generate these files with code.
Favicons
Favicons are small icons that represent your site in bookmarks and search results. To add a favicon to your application, create a favicon.ico and add to the root of the app folder.
![]()
You can also programmatically generate favicons using code. See the favicon docs for more information.
Static Open Graph images
Open Graph (OG) images are images that represent your site in social media. To add a static OG image to your application, create a opengraph-image.jpg file in the root of the app folder.

You can also add OG images for specific routes by creating a opengraph-image.jpg deeper down the folder structure. For example, to create an OG image specific to the /blog route, add a opengraph-image.jpg file inside the blog folder.

The more specific image will take precedence over any OG images above it in the folder structure.
Other image formats such as
jpeg,png, andgifare also supported. See the Open Graph Image docs for more information.
Generated Open Graph images
The ImageResponse constructor allows you to generate dynamic images using JSX and CSS. This is useful for OG images that depend on data.
For example, to generate a unique OG image for each blog post, add a opengraph-image.tsx file inside the blog folder, and import the ImageResponse constructor from next/og:
app/blog/[slug]/opengraph-image.tsx
JavaScriptTypeScript
import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/data'
// Image metadata
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
// Image generation
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return new ImageResponse(
(
// ImageResponse JSX element
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{post.title}
</div>
)
)
}
ImageResponse supports common CSS properties including flexbox and absolute positioning, custom fonts, text wrapping, centering, and nested images. See the full list of supported CSS properties.
Good to know:
- Examples are available in the Vercel OG Playground.
ImageResponseuses@vercel/og,satori, andresvgto convert HTML and CSS into PNG.- Only flexbox and a subset of CSS properties are supported. Advanced layouts (e.g.
display: grid) will not work.
API Reference
Learn more about the Metadata APIs mentioned in this page.
[
generateMetadata
Learn how to add Metadata to your Next.js application for improved search engine optimization (SEO) and web shareability.
](../../api-reference/functions/generate-metadata/index.md)[
generateViewport
API Reference for the generateViewport function.
](../../api-reference/functions/generate-viewport/index.md)[
ImageResponse
API Reference for the ImageResponse constructor.
](../../api-reference/functions/image-response/index.md)[
Metadata Files
API documentation for the metadata file conventions.
](../../api-reference/file-conventions/metadata/index.md)[
favicon, icon, and apple-icon
API Reference for the Favicon, Icon and Apple Icon file conventions.
](../../api-reference/file-conventions/metadata/app-icons/index.md)[
opengraph-image and twitter-image
API Reference for the Open Graph Image and Twitter Image file conventions.
](../../api-reference/file-conventions/metadata/opengraph-image/index.md)[
robots.txt
API Reference for robots.txt file.
](../../api-reference/file-conventions/metadata/robots/index.md)[
sitemap.xml
API Reference for the sitemap.xml file.
](../../api-reference/file-conventions/metadata/sitemap/index.md)[
htmlLimitedBots
Specify a list of user agents that should receive blocking metadata.
](../../api-reference/config/next-config-js/htmlLimitedBots/index.md)
Was this helpful?