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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsOption 1: add a static OG image
Use the file convention
- Create an image at the segment where it should apply, for example
app/opengraph-image.jpg. - Use a documented extension:
.jpg,.jpeg,.png, or.gif. - Keep the file at or below the documented 8 MB maximum. A larger
opengraph-imagefile causes a build failure. - 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.
#1 Best Overall
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.
Recommended Free Tools
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
- 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.
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.
Rank #3
Protect the image route
- Validate route parameters before constructing an upstream URL.
- Check
response.okand 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
- Run a production build with the same Next.js version used in deployment.
- Open a rendered page and inspect its HTML head for
meta property="og:image". - Open the emitted image URL directly. Confirm it returns an image content type, the intended dimensions, and non-empty bytes.
- 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.
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
- 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.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.
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:
Best Value
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.
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.
Quick Recap
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.

