DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
App Router

How to Add an Open Graph Image in Next.js (App Router)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In a current Next.js App Router project, add app/opengraph-image.png (or .jpg, .jpeg, or .gif) for a static image, or create app/opengraph-image.tsx and return new ImageResponse() for a generated image. Next.js discovers these special files and emits the Open Graph metadata for the route automatically. Use metadata.openGraph.images instead when an image already exists at an absolute URL.

Choose the right Next.js Open Graph method

Open Graph (OG) images are the previews shown when a URL is shared on platforms such as social networks and chat applications. The App Router offers four patterns:

Pattern Best for How Next.js finds it
Static special file One prepared image reused by a route or section opengraph-image.jpg, .jpeg, .png, or .gif in app or a route segment
Generated special file Branded or data-driven images rendered at request/build time opengraph-image.tsx returning ImageResponse
Metadata URL An image already hosted elsewhere metadata.openGraph.images with an absolute URL
Multiple generated variants Several OG images for one segment generateImageMetadata plus a generated image function

The examples below follow the App Router metadata conventions documented by Next.js. They do not apply the same way to the legacy Pages Router.

Add a static Open Graph image

Site-wide default

Put the file directly in the app directory:

app/opengraph-image.png

Next.js uses the file to create og:image metadata and derives the image type, width, and height. A static alternative text file can sit beside it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
app/opengraph-image.alt.txt

For a section, place the asset in that segment’s folder:

app/blog/opengraph-image.jpg

Segment-level files override a higher-level file. Thus, a post under app/blog/[slug] can have its own app/blog/[slug]/opengraph-image.png, which takes precedence over app/blog/opengraph-image.jpg and the root image. This lets you define a default once and replace it only where needed.

Static-file checklist

  • Use one of the supported extensions: .jpg, .jpeg, .png, or .gif.
  • Keep the Open Graph file at or below the documented 8 MB limit. Twitter image files have a separate 5 MB limit.
  • Place opengraph-image.alt.txt next to the image when static alt text is required.
  • Deploy the route and inspect the generated HTML for an absolute og:image URL.

Generate an image with opengraph-image.tsx

Create this file in the route segment that should own the image. The default export returns an ImageResponse from next/og; the named exports describe the generated metadata.

import { ImageResponse } from 'next/og'

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

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

The official example uses 1200 × 630 pixels, a broadly accepted sharing ratio. ImageResponse supports flexbox and a subset of CSS properties; it is not a general browser renderer, so CSS Grid and arbitrary browser features are not available in the documented renderer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use route parameters for post-specific cards

Put the generated file inside a dynamic segment, such as app/blog/[slug]/opengraph-image.tsx. In current Next.js 16 documentation, params resolves to a promise:

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={{ display: 'flex', fontSize: 64, padding: 48 }}>
      {title}
    </div>,
  )
}

Replace the slug transformation with a database or CMS lookup when you need the real post title. Keep that lookup reliable and fast; a failed data request can make the image route fail.

Point metadata at an existing image URL

If another service already hosts the image, export typed metadata from the route:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  openGraph: {
    images: [
      {
        url: 'https://example.com/og.png',
        width: 1200,
        height: 630,
        alt: 'Example article preview',
      },
    ],
  },
}

Each openGraph.images URL must be absolute, including the scheme and hostname. Relative paths such as /og.png do not satisfy this documented requirement. Width, height, and alt are optional metadata fields but are useful to consumers that read them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Generate multiple OG variants with generateImageMetadata

Use generateImageMetadata when one route segment needs more than one generated image—for example, language or campaign variants. Return an array containing each variant’s id, alt, size, and contentType. The default image function receives the selected id.

import { ImageResponse } from 'next/og'

export function generateImageMetadata() {
  return [
    {
      id: 'light',
      alt: 'Light theme preview',
      size: { width: 1200, height: 630 },
      contentType: 'image/png',
    },
    {
      id: 'dark',
      alt: 'Dark theme preview',
      size: { width: 1200, height: 630 },
      contentType: 'image/png',
    },
  ]
}

export default function Image({ id }: { id: string }) {
  const background = id === 'dark' ? '#111827' : '#ffffff'
  return new ImageResponse(
    <div style={{ display: 'flex', background, color: id === 'dark' ? 'white' : 'black', fontSize: 64 }}>
      {id} theme
    </div>,
  )
}

Understand precedence, caching, and freshness

Precedence follows the folder tree

A more specific image in a deeper route segment wins over an image higher in the tree. This is the key to combining a global default with section and post overrides.

Generated routes are cached by default

Next.js caches generated metadata routes by default. They can become dynamic when they use Dynamic APIs or uncached data. Decide deliberately whether a card should be stable and cacheable or regenerated from fresh content. If you fetch a post title without caching, expect dynamic behavior and account for the added latency and load.

Validate the public result

  1. Build and deploy the application.
  2. Open the route’s page source and confirm an absolute og:image value.
  3. Open the image URL directly to verify it returns the expected content type and dimensions.
  4. Share a URL in your target platform and allow for that platform’s own preview cache when testing changes.

Common failures and fixes

No og:image appears

Check spelling and location: the convention is exactly opengraph-image with a supported extension, or a correctly named opengraph-image.tsx. Confirm the file is under app rather than an unrelated directory and that the route is using the App Router.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The image URL is rejected

For metadata.openGraph.images, change a relative path to a complete absolute URL. Also verify that the deployed host is publicly reachable; private localhost URLs cannot be fetched by social crawlers.

The generated image throws a rendering error

Reduce the component to supported flexbox and documented CSS properties. Remove CSS Grid, unsupported fonts, browser-only APIs, and components that depend on client-side state. Return a valid ImageResponse from the default export.

Dynamic titles are stale

Inspect caching. A generated route is cached unless Dynamic APIs or uncached data make it dynamic. Choose an explicit caching strategy for your data source and redeploy or invalidate the relevant cache after a title change.

The file is too large

Keep opengraph-image output under 8 MB; Twitter images have a 5 MB maximum. Reduce embedded assets, simplify the layout, or choose a more compact format where appropriate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fonts or images do not render

Generated images run in a server-side renderer, not a full browser. Treat external assets as dependencies: use stable, reachable URLs or load them in a way supported by your deployment runtime, and test the deployed route rather than relying only on local development.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and design decisions

  • Static versus generated: static files are simplest and avoid render-time data work; generated routes provide per-post branding and text.
  • Build-time versus fresh data: cached output is faster and more predictable, while uncached data keeps cards current at the cost of work per request.
  • One image versus variants: use one default unless readers genuinely need multiple alternatives; generateImageMetadata adds flexibility but also more output to maintain.
  • Layout constraints: design with flexbox and supported properties first. A visually complex web page cannot be copied directly into ImageResponse.

Or skip the browser setup

If you only need a dependable screenshot of a rendered page, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners as a visitor, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and bills only clean shots. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; each response identifies the page verdict and billing status in headers.

One GET request returns PNG, JPEG, WebP, or PDF. The API supports full-page and element capture, device presets and custom viewports, retina scale, dark mode, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

See the ScreenshotNeo documentation for parameter details.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 shots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

Frequently Asked Questions

Can I use both a static file and metadata.images?

Yes, but define a deliberate precedence strategy in your route tree. A more specific special-file image can override a higher-level image, while metadata declarations provide an explicit hosted URL.

What dimensions should a generated OG image use?

The official ImageResponse example uses 1200 × 630 pixels. Keep the same ratio unless the platform or design system requires another size.

Does Next.js render arbitrary CSS in ImageResponse?

No. The documented renderer supports flexbox and a subset of CSS properties, so browser-only layouts such as CSS Grid should be redesigned.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.