GUIDE

Dynamic OG Images: Generating a Unique Preview Per URL

One static og:image works for a five-page site. The moment you have blog posts, products, or listings, every URL needs its own preview — here's what that requires.

August 16, 20266 min read

A dynamic og image generator solves a problem that only exists once a site has more than a handful of pages: the same og:image for every URL stops being acceptable the moment your content is actually different page to page. A blog with fifty posts sharing one generic banner image is leaving clicks on the table on every single share — the preview doesn't tell anyone which of the fifty posts they're about to open. This post is about that specific problem: per-URL image generation, not the general concept of OG images.

Where a single static image breaks down

Static og:image makes sense for a small marketing site — a homepage, a pricing page, an about page, maybe a handful more. There's little content variance between pages, so one well-designed image reasonably represents the brand across all of them. It stops making sense the moment your site has repeating content types: blog posts, each with a distinct headline; product pages, each with a distinct photo and price; job listings, each with a distinct title and location; documentation pages, each covering something different. Sharing the same image for all of them means every preview in a Slack channel or Twitter feed looks identical regardless of which specific post, product, or listing someone is actually sharing — which defeats much of the point of having a preview at all.

Blog posts

For a blog, dynamic generation typically means the post's own headline, and often its category or publish date, rendered onto a consistent template. This is usually the easiest content type to start with, since a blog post already has clean structured fields — title, excerpt, hero image, author — that map directly onto template variables. The main design decision is how much of the post's own hero image (if it has one) to incorporate versus relying purely on typography over a branded background; sites with strong photography usually lean toward incorporating the hero image, text-heavy or B2B sites usually lean toward typography-first templates that stay legible even without a photo.

Product pages

For product pages, the image variable usually is the product photo itself, with the template layering the product name, price, or a promotional badge on top. This case has a failure mode worth watching for specifically: products go out of stock, get renamed, or have their photos swapped, and if the OG image isn't regenerated when the underlying product data changes, you end up sharing a preview for a product that no longer looks or costs what the image says. Dynamic generation needs to be tied to the same data source the product page itself reads from, not a separate export that can drift out of sync with it.

Listings and user-generated content

Listings — real estate, job boards, marketplaces, classifieds — are the case where dynamic generation stops being a nice-to-have and becomes close to mandatory. A marketplace with thousands of user-created listings simply cannot have a human export an image per listing; the volume and the fact that listings are created by end users, not by anyone on the team, makes manual generation impossible by construction. Here the generator has to handle inputs it doesn't fully control — user-submitted photos of varying quality and aspect ratio, titles of unpredictable length, occasionally missing fields — gracefully, with fallback behavior for anything absent rather than breaking the render.

  • Blog posts: headline plus category, often layered over or beside the post's hero image
  • Products: product photo with name, price, or promotional badge overlaid
  • Listings: user-submitted data at unpredictable length and quality, requiring defensive fallback handling
  • Documentation: page title plus section, useful mainly for distinguishing pages within a large docs site

What breaks if dynamic generation isn't handled carefully

The two most common failure modes are stale images and render-time fragility. Stale images happen when the generation is tied to a publish-time snapshot that never updates again — a product's price changes six times after launch but the OG image still shows the original price from day one, because nothing re-triggers the render when the underlying data changes. Render-time fragility happens when a template that was only tested against clean sample data meets a real headline that's much longer than expected, or a user-submitted photo with an unusual aspect ratio, and the layout breaks — text overflowing its box, an image stretched or cropped in a way nobody designed for. A dynamic system has to be built assuming inputs will eventually hit every edge case, not just the ones used during template design.

Documentation and internal tools

A less obvious case worth mentioning is large documentation sites and internal tools, where dynamic previews are less about driving clicks and more about disambiguation. When a documentation link is shared in a support ticket or an internal Slack thread, a distinct preview per page — showing the section and specific page title rather than a generic "Docs" image — helps the recipient confirm at a glance that they're looking at the right page before clicking, which matters more in a large docs site where dozens of pages could plausibly cover a similar topic. This is a lower-stakes use case than a product catalog, but it's a real one, and it's often the first place a team notices the value of dynamic generation, since engineers sharing docs links internally hit the problem daily.

Deciding where to draw the line on dynamic coverage

Not every URL on a site needs a fully dynamic image, and treating dynamic generation as all-or-nothing usually means spending effort on pages where it doesn't matter. A reasonable way to decide is by share frequency and content distinctiveness together: pages that are shared often and differ meaningfully from each other (blog posts, products, listings) are worth full dynamic coverage. Pages that are shared occasionally but don't differ much from each other (a terms-of-service page, an internal settings page) are usually fine with a single static fallback image, and building dynamic support for them is effort spent on a case that was never actually a problem.

It's also worth revisiting that line periodically rather than setting it once. A page type that started out static and rarely shared can become a genuine sharing target later — a settings page that turns into a public-facing status page, for instance — and the right move at that point is upgrading it into the dynamic template rather than leaving it on a static fallback that no longer fits how it's actually being used.

In practice, this reassessment tends to happen naturally once analytics show a static-fallback page picking up meaningful share volume — that's the signal worth acting on, rather than trying to predict in advance which pages will eventually deserve dynamic treatment.

How useopengraph handles per-URL generation

This is the exact case useopengraph's template-and-variables model targets: you design one template with text, image, and color variables, and every page — post, product, or listing — renders its own version by supplying different values into the same layout, through a versioned render URL at /api/render/{templateId}/v{version}/.... Because rendering happens per set of variables rather than as a one-off manual export, adding a thousandth product page produces a correctly formatted, on-template OG image exactly the same way the first one did, with no additional design work and no risk of someone forgetting to generate it.

Stop paying per seat
for a usage-shaped problem.

Unlimited teammates, one usage pool. Start free with the scanner — no card required.