Image Generation vs Satori and @vercel/og: JSX Subset or Full API?

7 min read Image Generation

The Vercel OG Pipeline

If you’ve generated OG images in a Next.js project recently, you’ve probably used @vercel/og. Under the hood, it runs Satori — a library that converts JSX components to SVG, then passes the SVG through resvg-js to produce a PNG. No headless browser. No Puppeteer. It’s a clever piece of engineering, and for simple social cards on Vercel Edge Functions, it works well.

But Satori is not a browser engine. It implements a subset of CSS Flexbox layout, and that subset has sharp edges. The moment your design pushes past basic text-on-gradient territory, you start hitting walls.

The CSS Subset Problem

Satori’s documentation is upfront about what it doesn’t support. The list is long enough to matter.

No <style> tags. All styles must be inline. You can’t use CSS classes, external stylesheets, or any selector-based styling. Every element needs a style prop with a JavaScript object.

No z-index. Layers stack in DOM order. If you need to overlap elements in a specific order that doesn’t match your markup order, you’re restructuring your component tree to work around a layout constraint.

No backgroundSize: cover. Background images can’t be set to cover their container. If your design calls for a full-bleed photo behind text — one of the most common OG image patterns — you need workarounds.

No viewport units. No vw, vh, vmin, vmax. Responsive sizing relative to the image dimensions isn’t available.

No gap in Flexbox. You have to use margins or padding to space flex children. Minor, but it adds boilerplate to every layout.

No 3D transforms. No perspective, no rotateX/Y/Z, no transform-style: preserve-3d.

No advanced typography. No font-kerning, no font-variant-ligatures, no OpenType feature settings. If you need ligatures, small caps, or fine typographic control, Satori can’t help.

No RTL layout. Right-to-left languages like Arabic and Hebrew aren’t supported. If your product serves a global audience, that’s a hard blocker for localized OG images.

These aren’t obscure CSS features. z-index, backgroundSize: cover, and gap are things most developers use daily. Each missing feature means either a workaround or a compromise on the design.

The Font Problem

Fonts in Satori are another friction point. Satori supports TTF, OTF, and WOFF fonts only. No WOFF2 — the format most web fonts ship in today. No variable fonts — the format the industry is moving toward.

@vercel/og bundles Noto Sans by default. If you want any other font — and you almost certainly do for branded OG images — you need to fetch the font file at runtime and pass it as an ArrayBuffer. That means hosting font files, managing fetch logic, and dealing with edge function size limits.

// @vercel/og: loading a custom font
const interFont = await fetch(
  new URL("./Inter-Bold.ttf", import.meta.url)
).then((res) => res.arrayBuffer());

new ImageResponse(element, {
  width: 1200,
  height: 630,
  fonts: [{ name: "Inter", data: interFont, weight: 700 }],
});

Every custom font adds to your edge function bundle size. Use two weights of one font and you’re loading two separate files. Add a CJK font for Japanese or Chinese content and you’re looking at multi-megabyte font files that push against deployment limits.

The Image Generation API bundles 98 fonts — including Inter, Noto Sans, Noto Sans CJK (Japanese, Korean, Simplified and Traditional Chinese), Noto Sans Arabic, and dozens of others. You reference a font by name. No fetching, no bundling, no size limits.

React-Only, Vercel-Shaped

Satori requires JSX. Your image template is a React component. If your backend is Python, Go, Elixir, or anything other than a JavaScript runtime, you need a separate Node.js service just to generate images.

The components must also be pure and stateless. No useState, no useEffect, no dangerouslySetInnerHTML. No data fetching inside the component. This makes sense architecturally — Satori doesn’t run a React runtime — but it means your “React component” isn’t really a React component. It’s a markup-with-inline-styles template that happens to use JSX syntax.

@vercel/og wraps Satori into a package designed for Vercel Edge Functions. It handles the SVG-to-PNG conversion and returns an HTTP response. If you’re on Vercel and using Next.js, the integration is smooth. If you’re not on Vercel — if you deploy to AWS, Fly.io, Railway, or your own servers — you’re either using Satori directly (and handling the resvg conversion yourself) or bending your deployment to fit Vercel’s model.

The Image Generation API is a plain HTTP endpoint. Call it from any language, any framework, any hosting provider. TypeScript, Python, curl — whatever makes the request gets the image.

Layer Composition vs. JSX Layout

The Image Generation API doesn’t try to replicate CSS. It doesn’t use CSS at all. Instead, you compose 8 layer types — solid color backgrounds, gradients, rectangles, text, static images, QR codes, barcodes, and remote images — with explicit positioning.

Each layer has an x_in_px, y_in_px, width_in_px, and height_in_px. No layout engine figures out where things go. You tell each element exactly where to be. For generated images where you control the design, this is actually simpler than debugging why Flexbox put your title 3px too low.

Here’s an OG image using the TypeScript SDK:

import { IterationLayer } from "iterationlayer";

const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const result = await client.generate({
  width_in_px: 1200,
  height_in_px: 630,
  format: "png",
  layers: [
    { type: "gradient", direction: "linear", angle_in_deg: 45,
      stops: [
        { color: "#0f172a", position: 0 },
        { color: "#1e293b", position: 100 },
      ]},
    { type: "text", text: "**How We Cut Document Processing Time by 90%**",
      x_in_px: 60, y_in_px: 60, width_in_px: 800, height_in_px: 200,
      font_family: "Inter", font_size_in_px: 48, color: "#f8fafc",
      vertical_alignment: "top" },
    { type: "text", text: "iterationlayer.com",
      x_in_px: 60, y_in_px: 540, width_in_px: 400, height_in_px: 40,
      font_family: "Inter", font_size_in_px: 18, color: "#94a3b8" },
  ],
});

No font loading. No JSX. No CSS subset to memorize. The gradient layer does what backgroundSize: cover can’t. The text layers support Markdown formatting — bold, italic — without additional components.

What the API Adds Beyond Rendering

Satori renders JSX to SVG to PNG. That’s it. The Image Generation API includes capabilities that don’t have equivalents in the Satori pipeline.

QR codes and barcodes. Add a qr-code or barcode layer with the data you want to encode. No additional library, no separate generation step. Useful for event tickets, product labels, or marketing materials that need scannable codes alongside the visual design.

AI-powered smart crop. The API can detect the subject of an image and crop around it intelligently. Combine a remote image layer with smart crop for product images or profile pictures that always center on the subject — regardless of the original image’s composition.

Background removal. Strip the background from an image layer and composite it onto your design. Profile photos on branded backgrounds, product shots on clean gradients — without a separate background removal service.

98 bundled fonts. Already mentioned, but worth emphasizing. Noto Sans CJK alone covers Japanese, Korean, Simplified Chinese, and Traditional Chinese. Noto Sans Arabic covers Arabic script. You don’t think about font files. You pick a name from the list.

When Satori Is the Right Choice

Satori and @vercel/og are free and open source. If you’re building a Next.js app on Vercel and need basic OG images — title text on a colored background, maybe a logo — the integration is fast and the cost is zero. For that specific use case, it’s hard to argue against.

Satori also runs entirely on the edge, with no external API call. Your image generation has no network dependency beyond the initial deployment. For architectures where minimizing external calls matters, that’s a genuine advantage.

When the API Makes More Sense

The tradeoffs shift when any of these are true:

  • Your design uses CSS features Satori doesn’t support — z-index, backgroundSize: cover, viewport units, gap, or advanced typography.
  • You need fonts beyond Noto Sans and don’t want to manage font file loading and bundle sizes.
  • Your content includes CJK or Arabic text.
  • You’re not on Vercel, or not using Next.js, or not using JavaScript at all.
  • You need QR codes, barcodes, smart cropping, or background removal in the same image.
  • You want pixel-accurate output without the SVG-to-PNG conversion step introducing subtle differences from what you designed.

Each of these on its own is a workaround. Stack a few together and you’re spending more time fighting Satori’s constraints than building your product.

Get Started

Check out the Image Generation API docs for the full layer reference, font list, and SDK setup for TypeScript and Python. If you’ve been working around Satori’s CSS subset or wrestling with font loading in edge functions, try rebuilding one template with the layer-based approach. The constraints you’ve been designing around go away.

Iteration Layer runs on EU infrastructure (Frankfurt), which matters if your data residency requirements rule out US-hosted services.

Image Generation is one API in a composable suite — chain it with Document Extraction to turn parsed data into visual assets, or with Image Transformation to post-process the output. Same auth, same credit pool.

Sign up for a free account at iterationlayer.com/image-generation — no credit card required.

Start building in minutes

Free trial included. No credit card required.