Skip to main content

Dynamic Open Graph Images Explained

7 min read

Dynamic Open Graph images are generated at request time instead of stored as static files. Each page can ship a unique preview with the correct title, author, or product name baked into the graphic - without exporting hundreds of JPGs by hand.

If you run a blog, docs site, or SaaS app with many URLs, dynamic OG images are often the only scalable option.

Static vs. dynamic OG images

ApproachHow it worksBest for
StaticDesigner exports JPG/PNG, uploads to CDN, one URL in og:imageHomepages, campaigns, small sites
DynamicServer renders image from HTML/CSS or canvas when a crawler requests the pageBlogs, e-commerce, user-generated content

Both use the same meta tag:

<meta property="og:image" content="https://example.com/api/og?slug=hello-world">

The difference is whether that URL points to a file in storage or a route that builds the image on each request (often cached).

How dynamic generation works

  1. You define a template (layout, fonts, colors, logo).
  2. Your app passes page data (title, category, price, avatar).
  3. A server route renders a 1200 × 630 px image.
  4. The response is cached at the CDN or edge.
  5. Social crawlers fetch the URL like any other og:image.

Crawlers do not run JavaScript. The image must be a direct URL that returns Content-Type: image/png or image/jpeg with HTTP 200.

Next.js App Router

Next.js 13+ supports dynamic OG images via ImageResponse from next/og (built on Satori).

File-based route (common pattern)

Create app/opengraph-image.tsx next to a page or layout:

import { ImageResponse } from "next/og"
export const runtime = "edge"
export const alt = "Page preview"
export const size = { width: 1200, height: 630 }
export const contentType = "image/png"
export default async function Image() {
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#111",
color: "#fff",
fontSize: 64,
fontWeight: 700,
}}
>
Your Page Title
</div>
),
{ ...size }
)
}

Next.js automatically wires this to the page's Open Graph metadata. Each route can have its own opengraph-image.tsx.

Dynamic route with params

For [slug] pages, read the slug inside the image route, fetch the post title from your CMS, and render it in the template.

Metadata API alternative

You can also set openGraph.images in generateMetadata:

export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
return {
openGraph: {
images: [`https://example.com/api/og/${params.slug}`],
},
}
}

Point to a dedicated API route if you need query params or shared logic across frameworks.

Other stacks

Vercel OG / @vercel/og

Same Satori-based rendering, works in Edge Functions on Vercel and compatible hosts. Good for non-Next projects.

Puppeteer or Playwright

Render a real HTML page in headless Chrome and screenshot it. Flexible but slower and heavier than Satori. Cache aggressively.

Cloudinary / Imgix overlays

Store a base template in the CDN and add text overlays via URL parameters. No code route required, but less control over typography.

PHP, Ruby, Python

Use GD, Imagick, or Pillow to draw text on a template server-side. Older pattern, still reliable on WordPress plugins and Rails apps.

Caching is non-negotiable

Dynamic does not mean "render on every request forever." Without cache headers, your origin gets hammered every time someone shares a link.

Set long Cache-Control on the image response:

Cache-Control: public, max-age=31536000, immutable

Invalidate by changing the URL when content updates (/api/og/post-slug?v=2). See Open Graph Cache Busting.

Design constraints for generated images

Satori (used by Next.js) supports a subset of CSS flexbox. Not every Figma effect translates.

What works well

  • Flexbox layout
  • System fonts and embedded custom fonts (TTF/OTF)
  • Solid backgrounds, borders, border-radius
  • Inline images (fetch logo as ArrayBuffer)

What breaks

  • Complex CSS grid
  • Box shadows (limited)
  • Web fonts without explicit load
  • Animations (irrelevant for static PNG output)

Keep templates simple: background color, logo, one or two text lines. Match the design tips for readable headlines at 1200 × 630.

Testing dynamic OG images

Dynamic routes fail silently in production if fonts fail to load or the edge runtime throws.

  1. Open the og:image URL directly in a browser tab. You should see the image, not HTML or JSON.
  2. Check response headers: 200, correct Content-Type.
  3. Scan the page URL with OpenGraph Check to confirm crawlers receive the right image URL.
  4. Re-scrape in Facebook Sharing Debugger after template changes.

Common failure: the image route requires auth or blocks bots. Crawlers must reach it without cookies.

When static images are still better

  • One-off marketing landing pages with hand-crafted art
  • Campaigns where design review happens in Photoshop
  • Sites with no server-side rendering

For a 5-page brochure site, export PNGs and move on. For 500 blog posts, go dynamic.

No-code option: OG image generator

You do not need code to produce a solid template. OpenGraph Check's free OG image generator lets you design a 1200 × 630 image in the browser, add text and background, and download the file. Upload to your CDN and reference it in og:image.

Use the generator for static pages. Use dynamic generation when titles change per URL.

Common mistakes

Client-side canvas export only

Generating the image in the browser with <canvas> does not help crawlers unless you upload the result and set og:image to the hosted URL server-side.

Relative og:image paths

Dynamic routes still need absolute URLs:

<meta property="og:image" content="https://example.com/api/og?title=Hello">

Forgetting Twitter Card mirror

Set twitter:image to the same dynamic URL when you use large image cards. See Open Graph vs Twitter Card.

No fallback when generation fails

Return a default branded image if the CMS lookup fails. A generic fallback beats a 500 error and empty preview.

FAQ

What is a dynamic open graph image?

An og:image URL that generates a unique image per page at request time, usually from a server template, instead of a single static file for the whole site.

Does Next.js support dynamic OG images?

Yes. Use opengraph-image.tsx with ImageResponse from next/og, or an API route that returns PNG/JPEG bytes.

Are dynamic OG images slower for crawlers?

They can be, without caching. With CDN cache and edge rendering, response times match static files after the first request.

Can I use dynamic OG images on WordPress?

Yes, via plugins that generate images server-side, or custom PHP with Imagick. Headless WordPress often pairs with Next.js frontends that handle OG generation.

Do dynamic images work on LinkedIn and Facebook?

Yes, if the URL is public, returns 200, and serves a raster image under 1 MB. Test with OpenGraph Check and the Facebook Sharing Debugger.

How do I update a dynamic OG image after publish?

Change the template or bust cache with a new URL query string. Platforms cache aggressively; see Why Social Platforms Cache Link Previews.

Should og:image point to the dynamic route or a CDN copy?

Point directly at the dynamic route with strong cache headers. Optionally pre-render to storage on publish if your traffic volume demands it.

Bottom line

Dynamic Open Graph images let every URL carry a tailored preview without manual exports. Use Next.js ImageResponse, cache the output, keep templates simple, and test with OpenGraph Check. For one-off graphics, use the OG image generator. For hundreds of pages, automate.