ScreenshotNeo

BlogHow-to

How to Generate Open Graph Meta Tag Images

Create static or dynamic Open Graph images, connect them to metadata, and troubleshoot previews with runnable examples.

By the ScreenshotNeo team1 October 20265 min read

Direct answer: create a representative image, publish it at a publicly reachable absolute URL, and put that URL in og:image in the page’s <head>. Add the four required Open Graph properties—og:title, og:type, og:image, and og:url—plus accurate og:image:alt. For page-specific previews, generate the image from route data and return the generated file from an image endpoint.

The Open Graph specification defines metadata and image URLs; it does not generate image bytes or impose one universal size. Rendering can happen during your build, in a framework route, or through an image service.

1. Understand the metadata contract

Put Open Graph tags in the document head. The image URL must be absolute and point to the actual image resource.

<head prefix="og: https://ogp.me/ns#">
  <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/og-images" />
  <meta property="og:image" content="https://example.com/images/og/og-images.png" />
  <meta property="og:image:alt" content="A diagram showing an Open Graph image generated from page data" />
</head>

og:image:alt describes the image for accessibility; it is not a caption. Optional structured properties must match the file you serve:

<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

Width and height describe the resource; they do not resize it.

2. Choose static or generated images

Approach Use it when Trade-offs
Static files A small set of editorial pages or hand-designed artwork Simple deployment and review; each new page needs an asset
Build-time generation Page data is known during a site build Stable output and easy caching; data changes require a rebuild
Runtime route Many pages or frequently changing metadata One template scales across URLs; renderer limits must be handled

Vercel recommends 1200 × 630 pixels for its documented OG image workflow. Treat that as a Vercel recommendation, not a universal Open Graph requirement.

3. Create a static image

  1. Design an image that remains legible as a small social card.
  2. Export it in the format your host serves.
  3. Upload it to a stable public path such as /images/og/article-slug.png.
  4. Reference its absolute HTTPS URL from og:image.

4. Generate images dynamically

A dynamic route accepts page data, renders an image, and returns it with an image content type. Your page metadata points to that route.

Minimal Node.js route

import http from 'node:http';

function escapeXml(value) {
  return String(value)
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&apos;');
}

http.createServer((req, res) => {
  const url = new URL(req.url, 'http://localhost');
  if (url.pathname !== '/og.svg') {
    res.writeHead(404); res.end('Not found'); return;
  }
  const title = escapeXml(url.searchParams.get('title') || 'Untitled page');
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
    <rect width="1200" height="630" fill="#111827"/>
    <text x="80" y="290" fill="white" font-size="58" font-family="Arial, sans-serif">${title}</text>
  </svg>`;
  res.writeHead(200, {
    'Content-Type': 'image/svg+xml',
    'Cache-Control': 'public, max-age=3600'
  });
  res.end(svg);
}).listen(3000);

This demonstrates the route contract. If a target crawler requires PNG, render the same template to PNG and send Content-Type: image/png. Escape all untrusted values and cap their length.

Vercel’s documented renderer

Vercel’s OG image generation documentation describes passing changing values as endpoint parameters and producing PNG images from HTML/CSS with Satori and Resvg. Its implementation supports flexbox, not CSS grid; supports TTF, OTF, and WOFF fonts; and caps the bundle at 500 KB.

5. Add metadata in common stacks

Plain HTML

<meta property="og:title" content="<%= title %>" />
<meta property="og:type" content="article" />
<meta property="og:url" content="<%= canonicalUrl %>" />
<meta property="og:image" content="https://example.com/og?slug=<%= slug %>" />
<meta property="og:image:alt" content="Preview image for <%= title %>" />

Next.js

Next.js documents opengraph-image files and route-segment image generation. Follow the current Next.js metadata documentation for the exact API, then confirm the generated metadata contains an absolute image URL.

6. Verify before publishing

  • Fetch the final page HTML and confirm the tags are in <head>.
  • Request the image URL directly and confirm a 2xx response, correct Content-Type, and non-empty bytes.
  • Check that og:url is absolute and canonical.
  • Compare declared dimensions and MIME type with the actual file.
  • Use a versioned image URL when content changes; preview caching rules differ by platform.

7. Troubleshooting

Symptom Cause Fix
No preview Relative URL, blocked route, or non-image response Use an absolute HTTPS URL and return the correct image MIME type.
Old image appears Crawler cache Version the image URL and follow the platform’s refresh process.
Image is cropped Important content is near the edges Use safe margins and preview at small card sizes.
Dynamic route returns 500 Unsupported CSS, font, asset, or renderer exception Reduce the template to supported flexbox, include supported fonts, and test the route directly.
Tags are absent Metadata is added only after client-side JavaScript Emit tags during server rendering or static generation.
Broken output for some titles Unescaped user input Escape XML/HTML characters and enforce length limits.
Dimensions disagree Image was resized after metadata was written Regenerate metadata from the final artifact or omit optional dimensions.

8. Performance, reliability, and cost

  • Cache immutable outputs with a content hash or version.
  • Set renderer and external asset timeouts.
  • Limit query values and external downloads.
  • Make generation idempotent so crawler retries return the same bytes.
  • Account for rendering invocations, bandwidth, storage, fonts, and asset hosting. Open Graph itself does not define service pricing.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Capture a generated OG route or any public page with one request.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/og?title=Open%20Graph -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/og?title=Open Graph"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/og?title=Open%20Graph' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, failed loads, and cache hits are never billed. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account.

FAQ

Is 1200 × 630 required?

No. It is Vercel’s recommendation for its workflow. The Open Graph protocol sets no universal size.

Can og:image contain image bytes?

No. It contains a URL. Host the image or expose a route that returns it.

Is og:image:alt visible?

No. It is descriptive alternative text associated with the image.

Does every page need a unique image?

No. A shared representative image is valid when page-specific context is unnecessary.