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
| Approach | How it works | Best for |
|---|---|---|
| Static | Designer exports JPG/PNG, uploads to CDN, one URL in og:image | Homepages, campaigns, small sites |
| Dynamic | Server renders image from HTML/CSS or canvas when a crawler requests the page | Blogs, 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
- You define a template (layout, fonts, colors, logo).
- Your app passes page data (title, category, price, avatar).
- A server route renders a 1200 × 630 px image.
- The response is cached at the CDN or edge.
- 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.
- Open the
og:imageURL directly in a browser tab. You should see the image, not HTML or JSON. - Check response headers:
200, correctContent-Type. - Scan the page URL with OpenGraph Check to confirm crawlers receive the right image URL.
- 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.