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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Next.js supports Open Graph (OG) images in two ways: add an opengraph-image file to an App Router segment, or create an opengraph-image.js, .ts, or .tsx route that returns an image response. Next.js turns either convention into the corresponding metadata in the document head. Use a static file for a stable default; generate an image when titles, authors, prices, or other route data must appear in the graphic.

How Next.js chooses an Open Graph image

OG image files belong to an App Router route segment. A file in app/opengraph-image.jpg is the site-wide default. A file in app/blog/opengraph-image.png applies to the blog segment, and a deeper file such as app/blog/[slug]/opengraph-image.tsx is more specific for that route. The most specific matching segment wins over images in higher segments.

Next.js emits the image metadata for you; you normally do not need to write a separate openGraph.images entry in generateMetadata. The official overview is in the Next.js metadata and OG images guide, with convention details in the opengraph-image reference.

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

Option 1: add a static OG image

Use the file convention

  1. Create an image at the segment where it should apply, for example app/opengraph-image.jpg.
  2. Use a documented extension: .jpg, .jpeg, .png, or .gif.
  3. Keep the file at or below the documented 8 MB maximum. A larger opengraph-image file causes a build failure.
  4. Build and deploy the application, then inspect the rendered page head to confirm that Next.js emitted an OG image URL.

A root image is the simplest stable default. Put another file in a nested segment when a section needs a different visual. You can also add a separate twitter-image convention when you need a Twitter/X-specific asset; its documented file limit is 5 MB, distinct from the 8 MB opengraph-image limit.

When static is the right choice

  • The same artwork represents every page in a site or section.
  • You want no image-rendering code or data fetch.
  • Designers provide finished assets that already meet your dimensions and file-size requirements.

Option 2: generate an image with ImageResponse

Create a route named opengraph-image.tsx (or .js/.ts) in the relevant segment. Export a default function that returns ImageResponse from next/og. Optional alt, size, and contentType exports supply the matching metadata.

Static generated example

This file creates a 1200 × 630 PNG, the dimensions shown in the Next.js documentation example and ImageResponse defaults:

import { ImageResponse } from 'next/og'

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

export default function Image() {
  return new ImageResponse(
    (
      <div
        style={{
          background: '#111827',
          color: 'white',
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'center',
          padding: '80px',
          width: '100%',
          height: '100%',
        }}
      >
        <div style={{ fontSize: 64, fontWeight: 700 }}>Acme Engineering</div>
        <div style={{ fontSize: 34, marginTop: 24 }}>Practical software notes</div>
      </div>
    ),
    { ...size },
  )
}

ImageResponse uses @vercel/og, Satori, and Resvg to turn JSX-like HTML and CSS into a PNG. The documented API supports flexbox and a subset of CSS; advanced layouts such as CSS grid are not supported in the Next.js 15 reference. That reference also states a 500 KB maximum bundle size and support for TTF, OTF, and WOFF fonts. These are versioned constraints: check the reference for the Next.js version installed in your project before relying on them.

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

Generate an image for a dynamic route

Place the route beside the page it describes, such as app/posts/[slug]/opengraph-image.tsx. Fetch the post and render its title. In the current file-convention documentation for Next.js 16, params is a promise; older versions use a plain object, so match the signature to your installed release.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
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 post = await fetch(`https://example.com/api/posts/${slug}`, {
    next: { revalidate: 3600 },
  }).then((r) => {
    if (!r.ok) throw new Error(`Post request failed: ${r.status}`)
    return r.json() as Promise<{ title: string }>
  })

  return new ImageResponse(
    (
      <div style={{ display: 'flex', background: 'white', color: '#0f172a', padding: 72, width: '100%', height: '100%', fontSize: 56 }}>
        {post.title}
      </div>
    ),
    { ...size },
  )
}

Export an alt string when it improves the accessible description of the image. Keep the data response predictable and handle missing records deliberately; an exception in the image route can leave crawlers with no usable image.

Caching and rendering behavior

Generated metadata-image routes are cached and statically optimized by default. They are not automatically request-time functions. Request-time APIs, uncached data, or explicit dynamic configuration can change that behavior. Decide whether the image should update on every request, after a revalidation interval, or only when the application is rebuilt.

Choose a data strategy

  • Build-time or static: best for a fixed title or a small set of known pages.
  • Revalidated fetch: use a revalidation period when content changes occasionally and a cached image is acceptable.
  • Request-time: use only when the image must reflect immediately changing data and the route can afford the additional work.

Do not assume that changing source data instantly changes previews. Next.js controls generation and caching; social services maintain their own crawler and image caches. The framework documentation does not promise an immediate refresh on every external platform.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Design and implementation limits

Keep layouts within the renderer’s CSS subset

Use flexbox, explicit dimensions, and simple typography. Avoid CSS grid and browser-only APIs. If you load a font, include a supported TTF, OTF, or WOFF asset and account for the documented bundle-size limit in the Next.js 15 reference.

Protect the image route

  • Validate route parameters before constructing an upstream URL.
  • Check response.ok and provide a safe fallback title.
  • Keep remote fetches bounded with a cache or revalidation policy.
  • Compress static files below the 8 MB convention limit.
  • Use deterministic colors and text so a cached image remains valid for its page.

Verify that Next.js emitted the metadata

  1. Run a production build with the same Next.js version used in deployment.
  2. Open a rendered page and inspect its HTML head for meta property="og:image".
  3. Open the emitted image URL directly. Confirm it returns an image content type, the intended dimensions, and non-empty bytes.
  4. For a dynamic route, test a real slug, a missing slug, and a title containing punctuation or non-Latin characters.

These checks establish that your application serves the metadata and image. They do not establish when a particular social network will recrawl or display it.

Common failures and fixes

The build fails because of image size

For a static convention file, reduce or recompress the asset to 8 MB or less. Check that you are not confusing the separate 5 MB twitter-image limit with the 8 MB OG limit.

The generated route throws an error

Confirm that ImageResponse is imported from next/og, the component returns supported JSX and styles, and every upstream fetch checks its status. Replace unsupported CSS such as grid with flexbox.

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

The image is old after a content change

Review the route’s static optimization and fetch caching. A revalidation interval intentionally serves a cached result; a fully dynamic configuration has different performance and cost characteristics. Also allow for the external platform’s own cache, which is outside Next.js.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

The route works locally but not in production

Check that production has access to every remote API and font asset, that environment variables are present, and that the deployed Next.js version matches the code’s params signature. Build failures and runtime logs usually identify the first missing dependency or rejected request.

The preview is missing on a social platform

First verify the page head and image URL independently. Then check that the crawler can reach the deployed URL and that the image response is valid. Next.js documentation does not define each platform’s crawler, recrawl, or cache rules, so consult that platform’s current developer documentation for its refresh procedure.

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

Or skip the browser setup

If you need screenshots of a live page rather than a JSX-generated OG card, ScreenshotNeo provides a website screenshot API and MCP server. It accepts the cookie or consent banner like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

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

One GET request returns PNG, JPEG, WebP, or PDF. The API supports full-page and CSS-selector captures, lazy-image loading, dark mode, device presets, custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, 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 authentication and options. The following calls use the supplied API format:

cURL

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

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

The Free plan includes 1,000 shots each month without a card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Sign up free to try it.

ScreenshotNeo plans

Plan Price Included shots
Free $0 1,000/month, no card
Starter $5 3,000
Growth $15 15,000
Pro $39 60,000
Scale $99 250,000
Business $249 1,000,000

Yearly billing gives two months free. Clean shots are the only billed captures, which can make automated preview generation easier to account for than a service that bills failed loads.

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.

Frequently Asked Questions

Can I use both a static and generated opengraph-image?

Yes. Place them in different route segments; the most specific matching segment takes precedence.

Does Next.js require Vercel for OG images?

No such requirement is established by the file-convention documentation. The behavior described here is a Next.js App Router convention.

What dimensions should I choose?

The documented generated-image example uses 1200 × 630 pixels. Treat that as the Next.js example/default rather than a guarantee for every external platform.

Can ImageResponse render CSS grid?

The Next.js 15 reference says advanced layouts such as CSS grid are not supported; use flexbox and verify constraints for your installed version.

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.