NextjsOpen Graph & Dynamic OG Images

Open Graph & Dynamic OG Images

The image shown when a page is shared on social platforms, Slack, or iMessage comes from Open Graph (og:image) metadata. Next.js supports two ways to provide it: a static image file using a naming convention, or a dynamically generated image built at request time from a special route file.
Static OG image via file convention
Drop an image file named opengraph-image (with an extension like .png, .jpg, or .gif) into a route segment, and Next.js automatically picks it up and adds the right og:image meta tags — no metadata export needed.

File

Effect

app/opengraph-image.png

Default OG image for the whole site (used when a route doesn't define its own)

app/blog/[slug]/opengraph-image.png

A static OG image scoped to every post under that route (rare, since posts usually want unique images)

app/twitter-image.png

Same convention, specifically for the Twitter/X card image

Dynamic OG images with next/og
For images that need to include per-page content — a blog post title rendered over a branded background, for example — add a opengraph-image.tsx file that exports a default function returning an ImageResponse. Next.js renders it to an actual image at request time using JSX and CSS, similar to writing a small component.

app/blog/[slug]/opengraph-image.tsx

TSX
import { ImageResponse } from 'next/og'
import { getPost } from '@/libs/posts'

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

export default async function OGImage({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPost(params.slug)

  return new ImageResponse(
    (
      <div
        style={{
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'center',
          padding: '80px',
          background: '#0f172a',
          color: 'white',
          fontSize: 64,
          fontWeight: 700,
        }}
      >
        {post?.title ?? 'Untitled Post'}
      </div>
    ),
    { ...size }
  )
}
The exported size and contentType are optional but recommended — they let Next.js set accurate og:image:width/og:image:height tags without rendering the image first. Standard OG images are typically 1200×630.
What the framework generates for you
Whichever approach you use, Next.js automatically injects the matching <meta property="og:image"> and (for the dynamic case) a route that serves the generated image at a predictable URL — you don't write any of that wiring by hand, and you don't need to also set an images field inside a static metadata object when using either file convention.
Tip
Dynamically generated OG images are cached by default like any other route, so a given slug's image is only rendered once until the underlying data changes or the cache is invalidated — you get per-page personalization without paying the rendering cost on every share.
Note
next/og's ImageResponse supports a constrained subset of CSS (mostly flexbox-based layouts) since it renders via Satori rather than a full browser engine. Keep OG image JSX simple: flex containers, text, background colors/images, and basic typography.
  • A static opengraph-image.{png,jpg,gif} file in a route segment is picked up automatically with no metadata export needed.

  • A opengraph-image.tsx file exporting a function that returns ImageResponse from next/og generates a per-request image from JSX and CSS.

  • Export size and contentType alongside the dynamic image function so Next.js can set accurate dimension meta tags.

  • Next.js injects the og:image meta tags and serving route automatically for both approaches.

  • Dynamic OG images are cached like other routes, so repeat shares of the same page don't re-render the image each time.