The Blog

Better OG images from CMS fields

Technical SEOHeadless CMS

Default OG images are a missed click. Every page shares the same preview card on LinkedIn, Slack, and iMessage. Here's how to generate per-page OG images from Payload CMS fields using Next.js — so every shared link gets a preview that matches its content.

The problem

Every page shares the same preview card

When someone shares a link on LinkedIn, Slack, iMessage, or Teams, the platform fetches the page's Open Graph image to build a preview card. If every page on a site returns the same default OG image, usually a company logo on a brand-coloured background, every shared link looks identical.

That's a missed click. The preview card is the first impression. A blog post about redirects should look different from a case study about a home builder. A services page should look different from a tool page.

Most CMS setups handle OG images one of three ways. A single default image for the whole site. A manually uploaded image per page (which editors forget or skip). Or no OG image at all, which leaves the platform to guess, usually a random image from the page, or nothing.

The manual approach is what most Payload sites use, including ours currently. Each page has a meta.image field where editors can upload a custom OG image, with a site-wide default as the fallback. It works, but it creates a maintenance burden, and editors skip it more often than not.

There's a better pattern.

The pattern

Dynamic OG images from CMS fields

Next.js supports dynamic OG image generation through ImageResponse from next/og. Instead of requiring a manually uploaded image for every page, you can create a route that generates an OG image on the fly from fields already in the CMS.

The setup has three parts. A dynamic API route (something like /api/og?slug=/blog/broken-links-ci) that accepts a page slug. A Payload query inside that route that fetches the page's title, description, and content type, using select to keep the query lean. And an ImageResponse call that renders the image server-side as a PNG.

ImageResponse supports basic flexbox layout, custom fonts, and simple styling. No browser, no Puppeteer, no headless Chrome, just a function that returns pixels.

The generateMetadata function on each page template sets the openGraph.images property to point at this route with the current page's slug. When Next.js renders the page's <head>, the OG meta tags point to the dynamic image URL automatically.

Caching

Generate once, serve from cache

OG images don't change often, they're derived from the title and description, which editors update infrequently. The route should set appropriate cache headers so the image generates once and serves from cache on subsequent requests.

If you're using Payload's cache tag system for on-demand revalidation, you can tag the OG image response with the same tag as the page. When an editor updates a page's SEO fields, the page revalidates, and the OG image revalidates with it. New content, new preview card, no manual step.

Without cache tags, a reasonable stale-while-revalidate or time-based cache header works. OG images are fetched by platforms when someone shares a link, they're not loaded by the visitor's browser when viewing the page, so a slightly stale image has no performance impact on the site itself.

Fallback

Something should always render

A well-designed OG image setup has a fallback chain. If the editor has uploaded a custom image in the meta.image field, use that, it takes priority. If not, generate the image from the page's SEO title and description. If neither of those fields is populated, generate from the page title alone. And if all else fails, fall back to the site-wide default.

This means every page gets a preview card, even if nobody has touched the SEO fields. The generated image is better than the default, and a custom upload is better still. Editors can override when it matters, product launches, case studies, key landing pages, without being responsible for every page.

Typography

Consistent branding without system fonts

ImageResponse doesn't have access to your site's CSS or font stack. Custom fonts need to be loaded as ArrayBuffer and passed directly to the renderer. Most implementations fetch the font file at request time from a public URL or a local file.

The styling is limited to inline styles with flexbox layout, no CSS Grid, no complex positioning. But for an OG image, that's enough. A brand-coloured background, the page title in your heading font, a subtitle or content-type label, and your logo. Consistent across every page, different enough to be useful.

The standard OG image size is 1200×630 pixels. This works across all platforms without cropping

The preview card is the first impression. Every page should get one that matches its content.

FAQs

Common questions about dynamic OG images

LinkedIn, Facebook, Slack, Teams, iMessage, Discord, and most platforms that render link previews use Open Graph meta tags. Twitter/X uses its own twitter:card tags but falls back to OG. Set both.

No. OG images are fetched by the platform when someone shares a link, they're not loaded by the visitor's browser when viewing the page. The generation runs server-side and is cached after the first request.

Yes. The pattern works with any CMS that returns structured data from an API. The ImageResponse rendering is a Next.js feature, the CMS just provides the fields. Sanity, Strapi, Contentful, and Storyblok all work with this approach.

The fallback chain handles it. If SEO fields are empty, the image generates from the page title alone. If the page title is missing (unlikely), the site-wide default renders. Something always appears.

Different trade-off. Cloudinary and similar services offer URL-based image transformation and can generate text overlays dynamically. The Next.js approach keeps everything in your codebase with no external service dependency. For most Payload sites, the built-in approach is simpler and cheaper.

Little Dash

What does your site look like when someone shares a link?

We build CMS-driven sites on Payload and Next.js with metadata that works — from SEO fields to structured data to OG images.