Skip to main content
ImageResponse lets you generate dynamic images using JSX and CSS. This is useful for social media images such as Open Graph images and Twitter cards.
import { ImageResponse } from 'next/og'

new ImageResponse(
  element: ReactElement,
  options?: ImageResponseOptions
)

Parameters

element
ReactElement
required
A React element (JSX) describing the image layout. Only a subset of CSS and HTML is supported.
options
ImageResponseOptions
Optional configuration:

Supported CSS

ImageResponse supports common CSS including:
  • Flexbox layout (display: flex)
  • Absolute positioning
  • Custom fonts
  • Text wrapping and centering
  • Nested images
Advanced layouts like CSS Grid (display: grid) are not supported. For a full list of supported properties, see Satori’s documentation.

Behavior

  • Uses @vercel/og, Satori, and Resvg to convert JSX/CSS into PNG.
  • Maximum bundle size is 500KB (includes JSX, CSS, fonts, images, and other assets).
  • Supported font formats: ttf, otf, woff. Prefer ttf or otf for faster parsing.

Examples

In a Route Handler

app/api/og/route.tsx
import { ImageResponse } from 'next/og'

export async function GET() {
  try {
    return new ImageResponse(
      (
        <div
          style={{
            height: '100%',
            width: '100%',
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
            backgroundColor: 'white',
            padding: '40px',
          }}
        >
          <div style={{ fontSize: 60, fontWeight: 'bold' }}>
            Welcome to My Site
          </div>
        </div>
      ),
      { width: 1200, height: 630 }
    )
  } catch (e: unknown) {
    return new Response('Failed to generate image', { status: 500 })
  }
}

With opengraph-image.tsx

app/opengraph-image.tsx
import { ImageResponse } from 'next/og'

export const alt = 'My site'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

export default async function Image() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        My site
      </div>
    ),
    { ...size }
  )
}

With custom fonts

app/opengraph-image.tsx
import { ImageResponse } from 'next/og'
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'

export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

export default async function Image() {
  const interSemiBold = await readFile(
    join(process.cwd(), 'assets/Inter-SemiBold.ttf')
  )

  return new ImageResponse(
    (
      <div style={{ fontFamily: 'Inter', fontSize: 64, display: 'flex' }}>
        My site
      </div>
    ),
    {
      ...size,
      fonts: [
        {
          name: 'Inter',
          data: interSemiBold,
          style: 'normal',
          weight: 600,
        },
      ],
    }
  )
}

Version history

VersionChanges
v14.0.0ImageResponse moved from next/server to next/og.
v13.3.0ImageResponse importable from next/server.
v13.0.0ImageResponse introduced via @vercel/og.

Build docs developers (and LLMs) love