ScreenshotNeo

BlogHow-to

How to Set Open Graph Images in Next.js

Add a static or generated Open Graph image in Next.js App Router, or set one through metadata. Includes complete examples, version notes, and fixes.

By the ScreenshotNeo team4 October 20268 min read

To set an Open Graph image in the Next.js App Router, put an opengraph-image.jpg file in the route segment, create an opengraph-image.tsx file that returns an ImageResponse, or define openGraph.images in metadata or generateMetadata. Use the file convention for a fixed, route-colocated image; use a generated image for cards that depend on route data; use metadata when you already have an absolute image URL. Next.js generates the relevant head metadata for file-based images. Next.js documents the image conventions and limits.

1. Choose the right approach

Approach Best for Where it goes
Static image file A fixed image for a site or route app/opengraph-image.jpg or a nested route segment
Generated image A card with a title, author, category, or other route data opengraph-image.tsx in the segment
Metadata URL An image already hosted at a stable absolute URL metadata or generateMetadata

This guide covers the App Router. The file conventions were introduced in Next.js 13.3.0. Current Next.js 16 documentation uses promise-based route params in generated image functions; check your installed version before copying that signature. See the version history.

2. Add a static Open Graph image

For a site-wide default, place a supported file in the root App Router segment. For a route-specific image, put the file in that route’s segment. More specific files lower in the folder tree take precedence over a higher-level image.

app/
  opengraph-image.jpg
  page.tsx
  blog/
    opengraph-image.png
    page.tsx
    my-post/
      opengraph-image.jpg
      page.tsx

In this example, the root image is the default, the blog image applies to blog routes, and the post image applies to that specific post. Supported static extensions include .jpg, .jpeg, .png, and .gif. The current documented file limit for an Open Graph image is 8 MB; a larger file fails the build. To provide alternative text for a static image, add a sibling opengraph-image.alt.txt file containing the alt text.

app/blog/opengraph-image.alt.txt

Example contents:

Illustration of a developer publishing a blog post

Next.js emits the Open Graph image metadata from the convention. You do not need to manually add an og:image tag for this file.

3. Generate an image from route data

Create opengraph-image.tsx in the route segment and return an ImageResponse from next/og. The following route-specific example uses the promise-based params signature documented for Next.js 16:

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'

type Props = {
  params: Promise<{ slug: string }>
}

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

export default async function Image({ params }: Props) {
  const { slug } = await params
  const title = slug.replaceAll('-', ' ')

  return new ImageResponse(
    <div
      style={{
        width: '100%',
        height: '100%',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'center',
        padding: 64,
        background: '#101827',
        color: 'white',
        fontSize: 56,
      }}
    >
      <div style={{ color: '#93c5fd', fontSize: 28 }}>Example Blog</div>
      <div>{title}</div>
    </div>,
    { ...size }
  )
}

For a static label or site-wide card, you can omit route parameters. Export alt to describe the generated image, and export size and contentType so Next.js can describe the output:

export const alt = 'A preview card for the Example Blog'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

The dimensions above match the official example, not a universal requirement for every social network or messaging service. Generated images are statically optimized by default unless they use Dynamic APIs or uncached data. If the image depends on changing content, ensure the data and caching behavior match how often that content changes. See the generated image reference.

4. Set the image through metadata

Use a static metadata export for fixed metadata, or generateMetadata when the image URL depends on route parameters or fetched content. The image URL in the metadata example should be absolute.

// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  openGraph: {
    title: 'Example site',
    description: 'Articles and notes from Example site.',
    images: [
      {
        url: 'https://example.com/social/default-card.png',
        width: 1200,
        height: 630,
        alt: 'Example site preview card',
      },
    ],
  },
}

For route-dependent metadata:

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

type Props = { params: Promise<{ slug: string }> }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const post = await getPost(slug)

  return {
    title: post.title,
    openGraph: {
      title: post.title,
      description: post.description,
      images: [
        {
          url: `https://example.com/social/${slug}.png`,
          width: 1200,
          height: 630,
          alt: `Preview image for ${post.title}`,
        },
      ],
    },
  }
}

getPost is application-specific: replace it with your data lookup and handle a missing post according to your routing logic. Metadata exports work in Server Components. If you already have the image URL and do not need a colocated file, this approach keeps the image reference alongside the page metadata. See the Metadata API reference.

5. Preserve parent Open Graph fields

A child page that defines its own openGraph object replaces the parent’s openGraph object. Fields omitted in the child, such as a parent description or image, do not automatically carry over. Put shared values in a reusable object or include the values each child needs.

// app/shared-metadata.ts
import type { Metadata } from 'next'

export const siteOpenGraph: NonNullable<Metadata['openGraph']> = {
  siteName: 'Example site',
  title: 'Example site',
  description: 'Articles and notes from Example site.',
  images: ['https://example.com/social/default-card.png'],
}
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { siteOpenGraph } from '@/app/shared-metadata'

export async function generateMetadata(): Promise<Metadata> {
  const post = await getPost()

  return {
    title: post.title,
    openGraph: {
      ...siteOpenGraph,
      title: post.title,
      description: post.description,
      images: [`https://example.com/social/${post.slug}.png`],
    },
  }
}

Review nested metadata when adding a page-level image: retaining the shared object avoids accidentally dropping other Open Graph fields. The metadata docs describe inheritance behavior.

6. Verify the result

  1. Build or run the application and open the exact route that will be shared.
  2. Inspect the rendered document head and confirm it includes an og:image URL. For a file-convention image, use the generated URL in that tag.
  3. Open the image URL directly and confirm it responds with an image, not an error page or redirect to a private resource.
  4. Check that the image is publicly reachable after deployment. A local development URL cannot serve previews to external crawlers.
  5. When you change a URL-based image, verify that the deployed page points to the new URL. Social services may cache previews, so changing the file at the same URL may not immediately refresh every existing share.

For a quick visual check of a deployed page, you can capture its rendered appearance with a browser or screenshot tool. That checks what the page displays; separately inspect its HTML metadata and image URL, since a screenshot alone does not verify the Open Graph tags.

7. Common problems and fixes

Symptom Likely cause Fix
No og:image appears The file is outside the active App Router segment, uses an unsupported name or extension, or a child route has different metadata. Check the exact app route tree and inspect that route’s rendered head. Use the documented opengraph-image convention or add openGraph.images.
The image URL returns 404 The URL or route placement is wrong, or the deployed asset is missing. Open the emitted URL directly and correct the path or deployment contents.
Metadata uses the wrong image A more specific nested image takes precedence, or child metadata replaced parent Open Graph values. Check nested route files and explicitly merge shared metadata where needed.
Build fails on a static image The image exceeds the documented 8 MB Open Graph limit. Reduce or re-encode the asset below the limit and rebuild.
Generated route shows generic or stale content Route parameters, data lookup, or caching behavior do not match the content lifecycle. Check the installed Next.js signature, await promise-based params in v16, and review whether data is static or uncached.
Image works locally but not in shared previews The deployed URL is private, unreachable, or not the URL emitted in metadata. Use a publicly accessible deployed image URL and inspect the production page’s head.
Parent title or description disappeared The child defined a replacement openGraph object. Spread a shared Open Graph object and override only the fields that should change.

8. Performance, reliability, and cost

A static image has no route-specific rendering work and is a good fit when the same visual applies to a route segment. Generated images let each route have a tailored card, but their data lookup and rendering path must remain available and use suitable caching. Metadata URL references keep image generation outside the Next.js route, but the referenced host must continue serving the asset. The documented file size ceiling is 8 MB for Open Graph convention files; keep assets smaller where practical to reduce transfer time. The separate documented limit for a twitter-image file is 5 MB. Next.js metadata file limits.

These approaches use Next.js conventions or your existing image hosting and data infrastructure; the implementation itself does not require a screenshot API. A screenshot service can help inspect the deployed page visually, but it does not replace checking the emitted metadata.

Or skip the browser setup

If you need to capture a page while validating its deployed appearance, ScreenshotNeo is a website screenshot API and MCP server. It accepts a URL and returns an image or PDF. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. AI agents can take screenshots through its MCP server. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

How do I add an OG image to one Next.js page?

Put an opengraph-image file in that page’s route segment, or return a page-specific image from generateMetadata or opengraph-image.tsx.

Do I need to write the Open Graph tags manually?

No, when you use the App Router file convention Next.js generates the relevant metadata. With the Metadata API, return openGraph.images yourself.

Should I use an image file or generate the card?

Use a static file when the image is fixed. Generate the image when it needs route-specific content.

Does the example size guarantee the same display everywhere?

No. The 1200 × 630 size is the official example size in this dossier, not a universal platform guarantee.