OG Image Generators in 2026 — Open Source vs API

Every link you share on Twitter, LinkedIn, or Slack comes with a preview card — a 1200×630 image that either makes someone click or scroll past. That image is driven by the og:image meta tag, and for any site with dynamic content, generating it per page is a real engineering problem.

The tooling has matured significantly since 2022. You now have a credible open-source stack, several hosted API options, and strong opinions on both sides. This guide cuts through the noise: what each approach actually involves, where each breaks down, and a direct comparison to help you decide.

Why OG images matter more than most developers think

Studies consistently show that tweets and LinkedIn posts with custom images generate 2–3× more clicks than those with a generic fallback or no preview at all. The effect is even stronger on Slack and Discord, where the link unfurl is the dominant visual in a message thread.

The implication: a missing or broken OG image is not a cosmetic issue. For a blog with 50 posts, a SaaS with user profile pages, or an e-commerce site with thousands of product URLs, generic or absent previews measurably reduce traffic. Getting this right is an SEO and CTR lever, not a nice-to-have.

Social platforms cache OG images aggressively — often for days. Once a bad image is cached, it takes manual cache-busting via platform debug tools (Twitter Card Validator, LinkedIn Post Inspector) to fix it. Getting the image right on the first crawl matters.

Open source OG image generators

Vercel's @vercel/og (ImageResponse)

@vercel/og is built into Next.js as next/og and uses a custom JSX renderer backed by Satori under the hood. You write React-style JSX, and it produces a PNG at the edge — no Chromium, no network round-trip to a third-party service.

It works well for simple layouts with a title, a description, and a background. The moment you need a complex card — gradients behind clipped images, multi-column layouts, custom font weights — you hit the ceiling fast.

Satori (standalone)

Satori is the underlying library that powers @vercel/og. You can use it directly in any Node.js environment, giving you more control over the rendering pipeline. Satori converts a JSX tree to SVG, which you then rasterize with sharp or resvg-js.

html2canvas

html2canvas runs in the browser, not on the server. It walks the DOM and re-draws elements to a <canvas>. It is a client-side screenshot tool, not a server-side OG image generator — a common source of confusion.

For OG image generation specifically, html2canvas is the wrong tool. It shows up in search results because of its name, but it solves a different problem — client-side DOM capture, not server-side image generation.

Hosted API options

RenderPix

RenderPix accepts raw HTML and CSS via a POST request and returns a binary PNG, JPEG, or WebP. It runs a pre-warmed Chromium pool, so render times are consistently 80–200 ms with no cold start. The free tier covers 100 renders per month; paid plans start at $9/mo for 2,000 renders.

The key differentiator is the input format: raw HTML means full CSS support, any layout, any font (including Google Fonts loaded inline), and the ability to use your existing HTML templates without rewriting them in JSX.

Bannerbear

Bannerbear is a design-first tool aimed at marketers and no-code users. You build templates visually in their editor, then call an API with variables to fill in the template fields. Strong for teams that want a design tool in the loop; less suited for developers who want to ship HTML templates directly from code.

Cloudinary

Cloudinary is a full media management platform that includes dynamic image transformations via URL parameters. You can layer text and overlays onto a base image without an API call — just a URL. Extremely fast (CDN-backed), but the "template" is really a sequence of URL transformations, not HTML.

Head-to-head comparison

Tool Input format Full CSS support Vercel compatible Self-host effort Pricing
next/og (Satori) JSX subset No — CSS subset Yes — edge native None Free
Satori standalone JSX subset No — CSS subset Yes Medium — rasterize step Free
html2canvas DOM (client-side) Partial No — browser only None Free
RenderPix Raw HTML/CSS Yes — real Chromium Yes None Free tier, $9/mo
Bannerbear Visual template Via editor Yes None From $49/mo
Cloudinary URL parameters No — overlays only Yes None Free tier, usage-based

When to use open source

Open source is the right call when:

The hidden cost of open source is time. Satori's CSS subset is well-documented but still bites developers unexpectedly — a border-radius on an image works, but clip-path does not. Every time a new design requirement hits the ceiling, someone spends an afternoon figuring out the workaround.

When to use a hosted API

A hosted API earns its cost when:

At $9/mo for 2,000 renders, the API cost is negligible for most production workloads. A blog with 500 posts that re-renders OG images quarterly uses 500 renders per run — well within a single plan tier, and at a cost that is a rounding error against hosting and infrastructure.

The practical decision point: if you can express your design as straightforward JSX with Tailwind-compatible styles, start with next/og. If you find yourself fighting the CSS subset or need to reuse existing HTML templates, switch to a render API. Most teams that start with Satori migrate within a few months.

The migration path

If you are currently on next/og and want to migrate to a render API, the change is isolated to your Route Handler or generateMetadata function. The calling code is identical — both return a URL that resolves to a PNG. The only change is what happens at that URL: JSX rendered by Satori, or HTML rendered by Chromium.

tsx — before (next/og)
import { ImageResponse } from 'next/og';

export async function GET(req: Request) {
  const title = new URL(req.url).searchParams.get('title') ?? 'Untitled';
  return new ImageResponse(
    <div style={{ background: '#09090b', width: '100%', height: '100%',
      display: 'flex', alignItems: 'center', padding: '64px' }}>
      <h1 style={{ color: 'white', fontSize: 52 }}>{title}</h1>
    </div>,
    { width: 1200, height: 630 }
  );
}
tsx — after (RenderPix)
import { NextRequest } from 'next/server';

export async function GET(req: NextRequest) {
  const title = req.nextUrl.searchParams.get('title') ?? 'Untitled';
  const html = `<!DOCTYPE html><html><body style="
    margin:0; width:1200px; height:630px; background:#09090b;
    display:flex; align-items:center; padding:64px;
    font-family:'Inter',sans-serif;">
    <h1 style="color:white; font-size:52px;">${title}</h1>
  </body></html>`;

  const res = await fetch('https://renderpix.dev/v1/render', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-Api-Key': process.env.RENDERPIX_API_KEY! },
    body: JSON.stringify({ html, width: 1200, height: 630, format: 'png' }),
  });

  return new Response(await res.arrayBuffer(), {
    headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, s-maxage=86400' },
  });
}

The route URL, the generateMetadata reference, and every downstream consumer stay exactly the same. The swap is two files: the route implementation, and an environment variable.

Try RenderPix free — no credit card required.

100 renders per month on the free tier. Generate your first OG image in under two minutes.

← All articles API Reference →