How to Generate Open Graph Images for Your Website
Learn how to design or generate Open Graph images, add the right metadata, debug previews, and automate image capture for every page.

Direct answer: Create an image for each page, publish it at a stable public URL, and connect it in the document head with og:image. Add the other core Open Graph properties—og:title, og:type, and og:url—plus a useful og:description. You can maintain one static file for a small site or generate an image from page data when every article, product, or profile needs its own card.
This guide covers both approaches, complete metadata examples, a Next.js implementation, framework-independent generation, validation, troubleshooting, performance, and an automated screenshot option.
1. What an Open Graph image is
An Open Graph image is the image a page exposes to link-preview consumers through metadata. It is separate from the ordinary hero image that visitors see in the page body. You may deliberately use the same asset, but the relationship is made through the page head rather than through the visible layout.

The Open Graph Protocol defines four basic properties:
| Property | Purpose |
|---|---|
og:title |
The title displayed for the shared page. |
og:type |
The kind of object, such as website or article. |
og:image |
An absolute URL for the share-card image. |
og:url |
The canonical URL that identifies the page. |
og:description is optional in the protocol and generally recommended. Open Graph also defines structured image properties for MIME type, width, height, secure URL, and alternative text. Alt text describes the image; it is not a caption. See the Open Graph Protocol specification for the property definitions.
2. Choose static or generated images
Use a static image when
- You have a small number of stable pages.
- The same brand artwork can represent many URLs.
- A designer reviews and exports each card manually.
- You want the simplest deployment and caching behavior.
Generate an image when
- Titles, authors, products, prices, or categories differ per URL.
- You publish frequently and do not want a manual export step.
- You need a repeatable template with consistent typography and spacing.
- Your content system already has the data needed to render each card.
Static files provide direct art direction and a simple update workflow. Generated routes provide repeatability and page-specific output, but introduce rendering, font, data, and cache behavior that you must operate.
3. Design the image before writing code
Use a recognizable subject, strong contrast, and type that remains readable at thumbnail size. Keep the title short enough to scan and leave a safe margin around the edges because receiving clients can resize or crop cards differently.
Next.js uses 1200 by 630 pixels in its generated-image example. Treat that as a practical framework example rather than a universal requirement for every social network or messaging client. Check the current requirements for each destination you care about.
Prefer a small, compressed PNG, JPEG, or WebP when the receiving platform accepts it. If you use Next.js file conventions, its documentation lists an 8 MB ceiling for opengraph-image files and a 5 MB ceiling for twitter-image files. Those are framework-specific limits, not universal platform limits.
4. Add the required metadata
Put the tags in the rendered HTML document head:
<meta property='og:title' content='How to Generate Open Graph Images'>
<meta property='og:type' content='article'>
<meta property='og:url' content='https://example.com/guides/open-graph-images'>
<meta property='og:image' content='https://example.com/images/open-graph-images.png'>
<meta property='og:description' content='A practical guide to creating and debugging Open Graph images.'>
<meta property='og:image:type' content='image/png'>
<meta property='og:image:width' content='1200'>
<meta property='og:image:height' content='630'>
<meta property='og:image:alt' content='A diagram showing a web page becoming a social share card'>
Use an absolute HTTPS image URL that a crawler can request without an application session. Set og:type to match the page. Use the canonical URL in og:url, and keep title and description aligned with the page visitors will open.
5. Next.js App Router: static images
In a Next.js App Router project, place an image named opengraph-image.jpg, opengraph-image.jpeg, opengraph-image.png, or opengraph-image.gif in the route segment that owns the page. Next.js evaluates the file convention and emits the corresponding metadata. Add opengraph-image.alt.txt beside it when you need image alternative text.
For example:
app/
guides/
open-graph-images/
page.tsx
opengraph-image.png
opengraph-image.alt.txt
This is a good fit for a landing page or a small set of editorial routes. Replace the asset when the page’s subject changes, then inspect the deployed HTML to confirm the new URL is present.
6. Next.js App Router: generated images
For data-driven cards, create an opengraph-image.tsx route and return an ImageResponse. The documented API supports JSX-like content and a subset of CSS, including flexbox. CSS Grid and other unsupported layout features should not be assumed to work; check the current ImageResponse API reference.
import { ImageResponse } from 'next/og'
export const alt = 'Open Graph image for an article'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export default async function Image() {
return new ImageResponse(
(
<div
style={{
background: '#111827',
color: 'white',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '72px',
fontSize: 64,
}}
>
<div>How to Generate Open Graph Images</div>
<div style={{ fontSize: 30, marginTop: 24, color: '#a5b4fc' }}>
example.com
</div>
</div>
),
{ ...size }
)
}
Route parameters can supply a title, author, or category. Validate and escape that data, provide fallback text when a record is missing, and avoid fetching uncached data during the render unless you intentionally want dynamic behavior. Next.js documents generated images as statically optimized and cached by default unless dynamic APIs or uncached data are involved. Read the current metadata and OG image documentation for version-specific details.
7. Framework-independent generation
You can generate a PNG with any image renderer, store it in object storage or your public assets directory, and write the resulting URL into the page head. The essential contract is the same: the URL in og:image must resolve to the intended image when requested outside your browser session.
A minimal metadata helper in Node.js might look like this:
export function openGraphTags({ title, url, image, description, type = 'website' }) {
return `
<meta property='og:title' content='${escapeHtml(title)}'>
<meta property='og:type' content='${escapeHtml(type)}'>
<meta property='og:url' content='${escapeHtml(url)}'>
<meta property='og:image' content='${escapeHtml(image)}'>
<meta property='og:description' content='${escapeHtml(description)}'>
`
}
In production, use a real HTML escaping function and a template system that safely handles attribute values. Do not concatenate untrusted titles directly into markup.
8. Publish, inspect, and validate
- Deploy the page and image.
- Open the image URL directly in a private browser window.
- Fetch the page HTML and search the rendered head for
og:title,og:url, andog:image. - Confirm the image response has the intended content type and dimensions.
- Use the destination platform’s current preview or debugging tool when available.
- Share the exact production URL, then repeat after metadata changes.
Inspect deployed HTML rather than only your source template. Server rendering, routing, redirects, or environment configuration can prevent tags from reaching a crawler even when the source code looks correct.
9. Or skip the browser setup
If your goal is to capture a rendered page as an image—for example, to create a visual card from an existing route—ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF output. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.

See the ScreenshotNeo API documentation for all options.
cURL
curl -G 'https://api.screenshotneo.com/v1/shot' \
-d access_key=YOUR_API_KEY \
--data-urlencode 'url=https://example.com/article' \
-o shot.webp
Python
import requests
r = requests.get(
'https://api.screenshotneo.com/v1/shot',
params={
'access_key': 'YOUR_API_KEY',
'url': 'https://example.com/article',
},
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://example.com/article',
})
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`)
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`)
const buffer = Buffer.from(await res.arrayBuffer())
await Bun.write('shot.webp', buffer)
ScreenshotNeo supports full-page capture with lazy images loaded, element capture by CSS selector, dark mode, device presets, custom viewports, retina scale, custom CSS and JavaScript, click actions, hide selectors, selector or network-idle waits, blocked ads and trackers, custom headers and cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. The parameter names used by other screenshot APIs also work, which helps when migrating.
An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Free accounts include 1,000 shots per month without a card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
10. Troubleshooting Open Graph previews
| Symptom | Likely cause | Fix |
|---|---|---|
| No image appears | The tag is absent from rendered HTML or the URL is inaccessible. | Inspect production HTML, open the image URL directly, and verify HTTPS and redirects. |
| Old image remains | The receiving service has cached the previous metadata or asset. | Use its current re-scrape tool, change the asset URL when appropriate, and test again. |
| Wrong page title | A template emits stale data or multiple conflicting tags. | Render one intended value and remove duplicate Open Graph tags. |
| Image is cropped | The destination uses a different card layout or aspect ratio. | Keep important content inside safe margins and check that destination’s documentation. |
| Generated route returns an error | Missing route data, unsupported CSS, or a runtime-only dependency. | Add fallbacks, use supported flexbox styles, and test the route in the deployed runtime. |
| Image is too large | Uncompressed artwork or oversized embedded assets. | Resize, compress, and remove unnecessary metadata while staying within your framework’s documented limits. |
11. Performance, reliability, and cost
- Cache stable output. A static image or statically generated route avoids rendering on every crawler request.
- Version changed assets. A content hash or versioned filename makes updates predictable when consumers cache URLs.
- Keep generation deterministic. Pin fonts, colors, and layout inputs so the same page data produces the same card.
- Protect the renderer. Set timeouts, validate route parameters, and return a fallback image when optional data is unavailable.
- Measure the right cost. The dossier provides no universal platform conversion or performance benchmark. Compare your own render time, cache-hit rate, storage, and generation volume.
- Automate only where it helps. Static assets are cheapest operationally for a few pages; generated routes pay off when content volume makes manual design the bottleneck.
12. Checklist before sharing a URL
- One canonical
og:urlidentifies the page. og:title,og:type, andog:imageare present in rendered HTML.og:descriptionaccurately summarizes the page.- The image URL is absolute, public, stable, and served over HTTPS.
- The image has readable contrast and safe margins.
- Dimensions and MIME type match the generated file.
- Static or generated output has been tested after deployment.
- The intended platform’s current preview tool shows the expected card.
13. FAQ
How do I create an Open Graph image for my website?
Design or generate a PNG, JPEG, GIF, or another supported image, publish it at a public URL, and place that URL in an og:image tag in the page head. Use a static file for stable pages or a generated route for page-specific data.
Is 1200 by 630 required?
No universal requirement is established here. It is the size used in the current Next.js generated-image example and a practical starting point. Verify the current requirements of the platforms where your links will appear.
Can the Open Graph image be the same as my hero image?
Yes. They are separate concepts, so reuse the asset only when its composition remains legible as a small share card.
Why isn’t my link preview showing the right image?
Check the deployed HTML, the absolute image URL, the image response, duplicate tags, and the destination service’s cache. Re-scrape the URL with that service’s current debugging tool after correcting the page.
Should I generate cards at request time?
Only when the freshness requirement justifies the rendering cost. Static generation and caching are simpler and more reliable for most editorial pages.


