NEXT.JS

Why Next.js OG Images Render Blank or Broken in Production

You wire up /api/og/[slug] with @vercel/og, deploy, and the social preview comes back blank, or the layout collapses into one corner instead of the two-column design you built. You did not touch the JSX since your last working commit. Nothing crashed — the endpoint returns a 200 and a valid PNG, just not the one you designed. Ship that to production and every link shared on Twitter, LinkedIn, or Slack carries the broken version until someone forces the platform to re-crawl it, which can take days. Satori is the rendering engine inside @vercel/og, and it does not run your CSS the way a browser does — it implements a narrow, documented subset of it, and most broken renders trace back to one of three habits that work fine in ordinary React and silently fail here.

Why Does @vercel/og Render a Blank or Broken Image?

Most broken OG renders come from CSS that Satori does not implement, not from a bug in your template. Satori takes your JSX and inline styles and rasterizes them into a 1200×630 PNG without ever loading a browser engine, so any property that depends on layout behavior a real DOM provides — implicit block flow, CSS grid, floats — is silently ignored instead of throwing a build error.

That silence is what makes this hard to debug: a missing display value does not fail the render, it just collapses your two-column layout into an empty box. The fix starts with knowing exactly which properties Satori honors.

What CSS Does Satori Actually Support?

Satori supports flexbox layout, absolute and relative positioning, and standard box-model properties. It does not support CSS grid, float, or transitions and animations. The Satori project's own documentation states plainly that its CSS support is primarily flexbox, so anything built around grid or float needs to be rebuilt before it will render.

CSS featureWorks in SatoriWorkaround
Flexbox (display: flex)YesUse it for every layout, including single-child wrappers
CSS GridNoRebuild the layout with nested flex containers
FloatNoUse flex with justify-content instead
Absolute positioningYesFine for overlays and badges
Transitions and animationsNoNot needed — the output is a single static frame
Relative image URLsNoUse an absolute URL for every background image or img tag

Why Do You Need display: flex on Every Element?

Because Satori has no default block layout the way a browser does, any element wrapping more than one child needs an explicit display: flex or display: none, or its children collapse into the same point instead of stacking. This trips up anyone porting an existing React component, because ordinary block-level divs stack automatically in a browser and do not in Satori.

Working through this list template by template is a reasonable way to spend an afternoon once. Doing it for fifty page types, in twelve categories, with fonts and caching headers already sorted, is what BuyCoded's OG Image Generator Boilerplate is for — every template ships already fitted to Satori's subset, so the flex, font, and URL rules below are already applied for you.

How Do You Debug a Broken OG Render?

Open the endpoint directly in a browser tab first, at a URL like /api/og/[template]?title=test, because that shows the raw PNG output without any platform cache in the way. From there, work through the render in a fixed order rather than guessing at the template.

  1. Request the OG endpoint directly in a browser tab to see the raw output, bypassing any platform cache.
  2. Check the response status and content-type — a 500 or an HTML error page means the render threw, not just looked wrong.
  3. Remove CSS grid, float, and transition properties from the template one at a time and re-render after each removal.
  4. Add display: flex to every wrapping element, starting with the outermost container.
  5. Confirm every image and background-image URL is absolute, not relative.
  6. Re-test the final URL in Facebook's Sharing Debugger or LinkedIn's Post Inspector to force a fresh crawl.

Fonts and Image URLs Cause the Other Common Failures

The second most common failure after CSS is fonts. Satori cannot use a system font or a linked webfont, because there is no browser loading your stylesheet — every typeface has to be loaded as a font file and registered with the renderer before the JSX draws. A font that works everywhere else on your site falls back to nothing, or throws, if it is not loaded this specific way for the OG endpoint.

Image paths fail for the same underlying reason. A relative path like /logo.png resolves against a page's URL in a browser, but Satori has no browser and no current page to resolve against, so the same path silently returns nothing. Every background image or img source inside a Satori template needs a full absolute URL, protocol and domain included.

It is reasonable to ask whether paying for a boilerplate is worth it when the underlying fix is "read Satori's constraints and add display: flex." For a single template, it usually is not — you can fix one file by hand in an afternoon. The cost shows up once you have a dozen page types, each needing its own layout, fonts loaded correctly, and caching headers set so the image is not re-rendered on every crawler hit. At that point, tracking the same three failure modes across a dozen files by hand is the more expensive option, not a boilerplate built to avoid them from the start.

Does Caching Make a Broken OG Image Worse?

Yes, and this is the part most people miss. Once a platform crawls a broken render, it stores that PNG against your URL and keeps serving it from its own cache, not yours, for anywhere from a few hours to several days. Fixing the template on your end does nothing until the platform re-crawls, so the same broken preview keeps showing up on every new share of that link in the meantime.

Setting explicit cache-control headers on the OG endpoint response controls how long your own edge or CDN holds a render before checking for a new one, which shortens how long a bad deploy stays live on your side of the pipeline. It has no effect on a platform's own cache, which is why the manual re-crawl step in the debug list above still matters even after the header is fixed. A route deployed with sane defaults for headers and region removes one more place for this kind of drift to hide, on top of the CSS and font issues covered above.

Where to Start If You Are Rebuilding From Scratch

Rather than porting a browser layout to Satori's subset one template at a time, browse the BuyCoded catalog to see what is already built to these constraints before you commit to writing a template by hand. Every listing states exactly what is included and what it costs before you pay, so you can compare the hand-built path against the packaged one in a few minutes.

Frequently asked questions

Why does my OG image show up blank on Twitter but fine when I open the URL directly?

Twitter, Slack, and other platforms cache the first crawl of an og:image URL, often for days. If the image was broken the first time a crawler hit it, the platform keeps serving that broken version even after you fix the template. Force a fresh crawl with the platform's own debug tool, such as Facebook's Sharing Debugger or LinkedIn's Post Inspector, instead of just reloading the link yourself.

Can I use CSS Grid in a Satori-rendered OG template?

No. Satori's CSS subset does not include CSS grid, so a grid layout renders as if the property were never set, usually collapsing content into one column or stacking it in a corner. Rebuild the layout with nested flex containers instead — flexbox is fully supported and covers most grid-style layouts using row and column wrapping combined with sizing properties.

Why does my custom font not show up in the rendered image?

Satori runs without a browser, so it never loads your site's stylesheet or linked webfonts. Every font has to be read as a file and passed directly to the renderer as data before the image is drawn. A font that works everywhere else on your site silently falls back to nothing, or throws an error, if it is not loaded this way specifically for the OG endpoint.

Do I need Vercel to run a Satori-based OG generator?

No. Satori and @vercel/og run as a standard Node.js function despite the package name, so any host that supports a Node runtime for Next.js, including Render, Railway, Fly, or a plain VPS, can serve the same endpoint. Vercel is simply a common deploy target because its edge caching pairs well with image endpoints that rarely change per URL.

Why do relative image paths fail inside an OG template?

Relative paths like /logo.png resolve against a page's URL inside a browser, but Satori has no browser and no current page to resolve against, so the same path fails to load without an error. Every background image or img source inside a Satori template needs to be a full absolute URL, including the protocol and domain, or the image slot renders empty.

What image size should an OG template output?

1200×630 pixels is the standard size most platforms, including Twitter, Facebook, LinkedIn, and Slack, expect for a link preview image, and it is the size @vercel/og outputs by default. Going smaller risks a blurry upscale on high-density screens, and a different aspect ratio risks the platform cropping the design unpredictably instead of showing it as built.