Recommended Free Tools
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
The most direct way to generate route-specific Open Graph images in JavaScript is to add an opengraph-image.tsx file to the relevant Next.js App Router segment, load the route’s content, and return an ImageResponse from next/og. Export the image’s alt, dimensions, and MIME type. Next.js then creates the corresponding metadata tags and statically optimizes the result unless your data or configuration requires request-time rendering.
Generate an Open Graph image in Next.js App Router
For a blog route such as app/blog/[slug]/, create this file:
app/blog/[slug]/opengraph-image.tsx
A complete implementation looks like this:
import { ImageResponse } from 'next/og'
type Props = {
params: Promise<{ slug: string }>
}
export const alt = 'Article preview image'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
async function getPost(slug: string) {
const response = await fetch(`https://example.com/api/posts/${slug}`)
if (!response.ok) throw new Error('Post could not be loaded')
return response.json() as Promise<{
title: string
description?: string
category?: string
}>
}
export default async function Image({ params }: Props) {
const { slug } = await params
const post = await getPost(slug)
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '72px',
backgroundColor: '#101827',
color: '#ffffff',
fontFamily: 'Arial',
}}
>
<div style={{ display: 'flex', fontSize: 30, color: '#8ab4ff' }}>
{post.category ?? 'Blog'}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
<div style={{ display: 'flex', fontSize: 64, fontWeight: 700, lineHeight: 1.1 }}>
{post.title}
</div>
{post.description ? (
<div style={{ display: 'flex', fontSize: 28, color: '#c7d2e3' }}>
{post.description}
</div>
) : null}
</div>
<div style={{ display: 'flex', fontSize: 26 }}>example.com</div>
</div>
),
{ ...size },
)
}
The route parameter is supplied as a promise in the current file-convention API. Fetching by slug makes each article’s image different. The returned ImageResponse is a Response, so it satisfies the generated-image route contract.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Why export alt, size, and contentType?
altsupplies meaningful alternative text for the image metadata.sizedeclares the rendered width and height. The official example uses 1200 × 630; that is a practical example, not a universal social-network requirement.contentTypetells Next.js that the response is PNG. Use the MIME type that your renderer actually returns.
Next.js recognizes opengraph-image and twitter-image conventions, including .js, .ts, and .tsx generated-image files. The framework adds the related head tags automatically.
#1 Best Overall
Design within ImageResponse’s rendering model
ImageResponse uses @vercel/og, Satori, and resvg to turn JSX-like markup into a PNG. It is not a browser page. Next.js states: “Only flexbox and a subset of CSS properties are supported. Advanced layouts (e.g. display: grid) will not work.”
Safe layout rules
- Use explicit
widthandheighton the root element. - Use flexbox for rows, columns, alignment, and spacing.
- Avoid CSS Grid, browser-only DOM APIs, and components that depend on client-side layout.
- Keep text sizes and line heights explicit; long titles need a deliberate wrapping or truncation strategy.
- Use image dimensions explicitly. Remote images and fonts must be available to the rendering runtime.
Satori accepts pure, stateless JSX and implements its own SVG-oriented layout engine, so browser CSS output is not guaranteed to match the generated image. Validate every design against the supported element and style lists.
Load a local font
Fonts can be passed as buffers or ArrayBuffer values in the ImageResponse options. A file-convention example using Node’s filesystem API is:
import { readFile } from 'node:fs/promises'
import { ImageResponse } from 'next/og'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export const alt = 'Product announcement'
export default async function Image() {
const font = await readFile('./public/fonts/Inter-Bold.ttf')
return new ImageResponse(
<div style={{ display: 'flex', fontSize: 72 }}>New release</div>,
{
...size,
fonts: [
{
name: 'Inter',
data: font,
weight: 700,
style: 'normal',
},
],
},
)
}
Check that the selected deployment runtime permits the Node APIs you use. If you use Satori directly rather than through Next.js, its documented runtime support includes Node.js 16 or later, browsers, and Web Workers. In runtimes that restrict dynamic WebAssembly loading, Satori provides a standalone build that accepts separately loaded yoga.wasm.
Choose build-time or request-time generation
Generated images are statically optimized—generated at build time and cached—by default. That is efficient for content that changes when you deploy. Request-time APIs, uncached data, or explicit dynamic configuration change that behavior.
Rank #2
Build-time and cached output
- Best for published posts, product records, and other content with predictable invalidation.
- Fetch data in a way that remains cacheable, then redeploy or revalidate when titles or artwork change.
- Expect the first build to render every required route.
Request-time output
- Use it when the image must reflect rapidly changing data or per-request state.
- Account for data-source latency and failures on every request.
- Do not accidentally invoke request-time APIs if you intended static generation.
Decide when an image becomes stale before choosing your fetch and route configuration. A cache policy is part of the image design, not an afterthought.
Static image files and size limits
If the artwork does not need code, place a literal image in the route segment using the documented convention. Next.js accepts JPEG/JPG, PNG, and GIF files and adds the metadata tags. An accompanying .alt.txt file can provide the image’s alt text.
Next.js documents an 8 MB maximum for a static opengraph-image file; exceeding it fails the build. The corresponding documented limit for a static twitter-image file is 5 MB. These are Next.js file-convention constraints, not a complete statement of every social platform’s limits.
Use Satori without Next.js
Satori is the framework-independent option when you want JSX-to-SVG rendering directly. It accepts pure JSX-like elements, uses a deliberately limited layout implementation, and returns SVG. If your endpoint must return PNG, add a separate SVG-to-raster step such as the renderer supplied by your chosen runtime.
This approach gives you control over the HTTP endpoint and cache, but you must implement route parsing, metadata headers, font loading, image loading, error handling, and raster conversion yourself. Compare those responsibilities with the convenience of Next.js’s ImageResponse.
Cloudflare Pages integration
Cloudflare Pages documents @cloudflare/pages-plugin-vercel-og as middleware for rendering social images. The plugin can extract an existing page’s og:title and pass it to a renderer component. Its autoInject.openGraph option can add og:image, width, and height metadata. The API can also create arbitrary images directly, and the official example returns a 1200 × 630 ImageResponse.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteThis is a documented Cloudflare Pages route, not evidence that every hosting runtime exposes identical APIs. Check runtime support for fonts, fetches, WebAssembly, and caching before moving a Next.js implementation.
Expose and verify the metadata
After deployment, inspect the generated HTML for the route. You should find an og:image URL and, where your metadata configuration supplies them, matching width, height, and alt values. Also request the image URL directly and verify the response’s MIME type, dimensions, and status.
Verification checklist
- Open the exact route URL, not only the site home page.
- Confirm the title, description, and image URL are route-specific.
- Request the generated image without browser-only cookies or JavaScript.
- Test a slug containing non-ASCII characters, punctuation, and a very long title.
- Check a missing slug and confirm your intended 404 or fallback behavior.
- Rebuild after changing content to confirm your invalidation strategy.
Troubleshooting common failures
CSS layout is missing or distorted
Cause: unsupported CSS, especially Grid or browser-dependent styling. Fix: replace it with flexbox, explicit dimensions, supported properties, and simpler JSX.
The image is blank or times out
Cause: a remote font or image cannot be fetched by the rendering runtime, or the data request is slow. Fix: serve assets from an accessible URL or bundle them, add explicit timeouts and error handling, and return a deliberate fallback design.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #4
Every route shows the same image
Cause: the route parameter was not awaited or was not used in the data lookup. Fix: read the promised params, fetch by the resulting slug, and ensure the cache key includes that route.
New content does not appear
Cause: static generation or a cache still contains the earlier response. Fix: choose revalidation or request-time rendering intentionally and invalidate the relevant deployment or cache.
The build fails on an image file
Cause: a static file exceeds the documented 8 MB Open Graph limit (or 5 MB Twitter limit). Fix: reduce dimensions or quality, or generate a smaller encoded asset.
Performance, reliability, and cost decisions
- Static generation: shifts rendering to build time and makes delivery cache-friendly, but requires a refresh strategy.
- Request-time rendering: reflects current data, but adds origin latency and introduces failure modes for every share crawler request.
- Remote assets: reduce bundle size but depend on network access and stable URLs. Local fonts and deterministic artwork are usually easier to reproduce.
- Complex designs: increase the chance of unsupported CSS and rendering differences. Keep social cards legible at thumbnail size.
- Output format: PNG is the documented
ImageResponseexample; choose another format only when your renderer and consuming platforms support it.
Or skip the browser setup
If you need screenshots of a live page rather than a generated social card, ScreenshotNeo provides a single-call website screenshot API and MCP server. It accepts cookie and consent banners before capture, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and bills only clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Each response identifies the result with X-Page-Verdict and X-Billed headers.
Use the API documentation at https://screenshotneo.com/docs/ for all options. A minimal call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent JavaScript and Python calls:
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}`);
For AI workflows, its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. It also supports full-page and element captures, device presets, custom viewports, retina scale, PDFs, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification.
Best Value
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account to try it.
Frequently Asked Questions
Can I use a generated image for both Open Graph and Twitter cards?
Yes. Add the generated-image conventions and metadata needed by each network; Next.js supports separate opengraph-image and twitter-image files when you need different artwork or limits.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Does Satori return PNG files?
Satori renders JSX-like input to SVG. Add a separate rasterization step when your endpoint must return PNG; Next.js ImageResponse supplies that pipeline for its generated-image routes.
Should a social image be generated in the browser?
Usually no. Generate it on the server or at build time so crawlers can fetch a stable image URL without running client JavaScript.
What happens when a post title is too long?
Implement an explicit truncation or wrapping rule in the JSX and test the longest real titles, because the renderer does not provide normal browser layout behavior.
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.

