opengraph-image and twitter-image

API Reference for the Open Graph Image and Twitter Image file conventions.

The opengraph-image and twitter-image file conventions allow you to set Open Graph and Twitter images for a route segment.

They are useful for setting the images that appear on social networks and messaging apps when a user shares a link to your site.

There are two ways to set Open Graph and Twitter images:

Precedence with metadata exports

These rules apply to both static image files and generated images, following metadata ordering:

  • Page metadata: A page's explicit openGraph.images or twitter.images from its metadata object or generateMetadata function overrides the corresponding opengraph-image or twitter-image file. This includes files in the same segment and images inherited from parent segments.
  • Layout metadata: A page's colocated image file can replace images configured by a layout in the same or a parent segment. This applies when the page omits the corresponding images property.

For example, if app/blog/page.tsx sets openGraph.images to ['/custom-og.png'], the page uses /custom-og.png even when app/blog/opengraph-image.png exists.

Image files (.jpg, .png, .gif)

Use an image file to set a route segment's shared image by placing an opengraph-image or twitter-image image file in the segment.

Next.js will evaluate the file and automatically add the appropriate tags to your app's <head> element.

File conventionSupported file types
opengraph-image.jpg, .jpeg, .png, .gif
twitter-image.jpg, .jpeg, .png, .gif
opengraph-image.alt.txt
twitter-image.alt.txt

Good to know:

The twitter-image file size must not exceed 5MB, and the opengraph-image file size must not exceed 8MB. If the image file size exceeds these limits, the build will fail.

opengraph-image

Add an opengraph-image.(jpg|jpeg|png|gif) image file to any route segment.

<head> output
<meta property="og:image" content="<generated>" />
<meta property="og:image:type" content="<generated>" />
<meta property="og:image:width" content="<generated>" />
<meta property="og:image:height" content="<generated>" />

twitter-image

Add a twitter-image.(jpg|jpeg|png|gif) image file to any route segment.

<head> output
<meta name="twitter:image" content="<generated>" />
<meta name="twitter:image:type" content="<generated>" />
<meta name="twitter:image:width" content="<generated>" />
<meta name="twitter:image:height" content="<generated>" />

opengraph-image.alt.txt

Add an accompanying opengraph-image.alt.txt file in the same route segment as the opengraph-image.(jpg|jpeg|png|gif) image its alt text.

opengraph-image.alt.txt
About Acme
<head> output
<meta property="og:image:alt" content="About Acme" />

twitter-image.alt.txt

Add an accompanying twitter-image.alt.txt file in the same route segment as the twitter-image.(jpg|jpeg|png|gif) image its alt text.

twitter-image.alt.txt
About Acme
<head> output
<meta name="twitter:image:alt" content="About Acme" />

Generate images using code (.js, .ts, .tsx)

In addition to using literal image files, you can programmatically generate images using code.

Generate a route segment's shared image by creating an opengraph-image or twitter-image route that default exports a function.

File conventionSupported file types
opengraph-image.js, .ts, .tsx
twitter-image.js, .ts, .tsx

Good to know:

The easiest way to generate an image is to use the ImageResponse API from next/og.

import { ImageResponse } from 'next/og'
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
 
// Image metadata
export const alt = 'About Acme'
export const size = {
  width: 1200,
  height: 630,
}
 
export const contentType = 'image/png'
 
const interSemiBold = await readFile(
  join(process.cwd(), 'assets/Inter-SemiBold.ttf')
)
 
// Image generation
export default async function Image() {
  return new ImageResponse(
    (
      // ImageResponse JSX element
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        About Acme
      </div>
    ),
    // ImageResponse options
    {
      // For convenience, we can re-use the exported opengraph-image
      // size config to also set the ImageResponse's width and height.
      ...size,
      fonts: [
        {
          name: 'Inter',
          data: interSemiBold,
          style: 'normal',
          weight: 400,
        },
      ],
    }
  )
}
<head> output
<meta property="og:image" content="<generated>" />
<meta property="og:image:alt" content="About Acme" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

Props

The default export function receives the following props:

params (optional)

A promise that resolves to an object containing the dynamic route parameters object from the root segment down to the segment opengraph-image or twitter-image is colocated in.

Good to know: If you use generateImageMetadata, the function will also receive an id prop that is a promise resolving to the id value from one of the items returned by generateImageMetadata.

export default async function Image({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  // ...
}
RouteURLparams
app/shop/opengraph-image.js/shopundefined
app/shop/[slug]/opengraph-image.js/shop/1Promise<{ slug: '1' }>
app/shop/[tag]/[item]/opengraph-image.js/shop/1/2Promise<{ tag: '1', item: '2' }>

Returns

The default export function should return a Response.

Good to know: ImageResponse satisfies this return type.

Config exports

You can optionally configure the image's metadata by exporting alt, size, and contentType variables from opengraph-image or twitter-image route.

OptionType
altstring
size{ width: number; height: number }
contentTypestring - image MIME type

alt

export const alt = 'My images alt text'
 
export default function Image() {}
<head> output
<meta property="og:image:alt" content="My images alt text" />

size

export const size = { width: 1200, height: 630 }
 
export default function Image() {}
<head> output
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

contentType

export const contentType = 'image/png'
 
export default function Image() {}
<head> output
<meta property="og:image:type" content="image/png" />

Route Segment Config

opengraph-image and twitter-image are specialized Route Handlers that can use the same route segment configuration options as Pages and Layouts.

Examples

Using external data

This example uses the params object and external data to generate the image.

Good to know: By default, this generated image will be statically optimized. You can configure the individual fetch options or route segments options to change this behavior.

import { ImageResponse } from 'next/og'
 
export const alt = 'About Acme'
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 fetch(`https://.../posts/${slug}`).then((res) =>
    res.json()
  )
 
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 48,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        {post.title}
      </div>
    ),
    {
      ...size,
    }
  )
}

Using Node.js runtime with local assets

These examples use the Node.js runtime to fetch a local image from the file system and pass it to the <img> src attribute, either as a base64 string or an ArrayBuffer. Place the local asset relative to the project root, not the example source file.

The asset doesn't depend on request data, so read it once at module scope. See Predictable values.

import { ImageResponse } from 'next/og'
import { join } from 'node:path'
import { readFile } from 'node:fs/promises'
 
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
const logoSrc = `data:image/png;base64,${logoData}`
 
export default async function Image() {
  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        <img src={logoSrc} height="100" />
      </div>
    )
  )
}

Passing an ArrayBuffer to the src attribute of an <img> element is not part of the HTML spec. The rendering engine used by next/og supports it, but because TypeScript definitions follow the spec, you need a @ts-expect-error directive or similar to use this feature.

import { ImageResponse } from 'next/og'
import { join } from 'node:path'
import { readFile } from 'node:fs/promises'
 
const logoData = await readFile(join(process.cwd(), 'logo.png'))
const logoSrc = Uint8Array.from(logoData).buffer
 
export default async function Image() {
  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        {/* @ts-expect-error Satori accepts ArrayBuffer/typed arrays for <img src> at runtime */}
        <img src={logoSrc} height="100" />
      </div>
    )
  )
}

Troubleshooting

A file-based image isn't used

If a page uses a different image from its colocated opengraph-image or twitter-image file:

  1. Inspect the page's rendered og:image or twitter:image meta tag to find the selected image URL. A file-based image route can still serve an image even when the page's metadata points elsewhere.
  2. Check for openGraph.images or twitter.images in the page's metadata export, including properties copied from shared objects.
  3. Omit the corresponding images property entirely to use the colocated file.

Good to know: Setting images to [], null, or undefined still overrides the file-based image. For Twitter, only [] also suppresses fallback to resolved Open Graph images.

An inherited image disappears

When a child route defines openGraph or twitter without images and has no colocated image file, it can lose inherited images. See metadata merging for how to retain inherited fields.

Version History

VersionChanges
v16.0.0params is now a promise that resolves to an object
v13.3.0opengraph-image and twitter-image introduced.