Library / SDK
vercel/satori avatar
vercel/satori

vercel/satori: rendering HTML and CSS to SVG without a browser

Enlightened library to convert HTML and CSS to SVG

13,991 stars371 forksTypeScriptMPL-2.0

At a glance

What is it?
Satori is a TypeScript library that turns JSX and a CSS subset into an SVG string using its own layout engine. It fits Open Graph image pipelines that need per-request rendering without a headless browser, and it is the wrong tool for pages that depend on full CSS.
Who is it for?
Adopt Satori if you generate Open Graph images, social cards or other fixed-size artwork per request and want the output as an SVG string without running a browser. Do not adopt it if your markup depends on the full CSS box model, media queries, external stylesheets or interactive elements; the README states it supports only a static, visible subset.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What Satori solves for Open Graph image pipelines

Rendering a social card server-side usually means either shipping a headless browser or hand-writing SVG. The first is heavy per request; the second is brittle the moment a designer changes a layout. Satori takes a third path: you write JSX with inline styles, and it returns an SVG string. The README describes it as a library to convert HTML and CSS to SVG, and the homepage is an OG playground where you can paste markup and see the result.

The audience is narrow and identifiable. The README points people who want PNG output for Open Graph images and social cards at Vercel's OG image generation announcement and docs, and at Next.js examples. So the intended user is a developer generating images at request time, typically inside a serverless function, where starting a browser is not an option. If you are building a general HTML-to-image service for arbitrary pages, this is not the same problem, and Satori's constraints will bite quickly.

The layout engine inside Satori and why the CSS subset is small

Satori does not parse CSS the way a browser does. It runs the same Flexbox layout engine as React Native, Yoga, and the repository ships a compiled yoga.wasm at the top level, which is also exposed as a package export. Layout, font handling and typography are computed in the library, and the output is an SVG that attempts to match the HTML and CSS you wrote.

The CSS table in the README is explicit about the boundaries. display accepts flex, block, contents, none and -webkit-box, defaulting to flex, and the README warns that div elements with multiple child nodes should use flex, contents or none. position accepts relative, static and absolute, defaulting to relative. CSS variables work, including var(--var-name) with fallback values. min-width and min-height are supported except for min-content, max-content and fit-content. Elements such as input are out of scope, as are cursor, style tags, and external resources via link or script.

That is a deliberate trade. Satori implements its own layout engine based on the SVG 1.1 spec, and the README states plainly that it does not guarantee a 100 percent match with browser-rendered HTML. Treat the SVG as a close approximation produced under a documented subset, not as a rendering of your site.

Installing Satori and rendering a first SVG

Satori is published to npm as satori. The README shows it imported as an ES module, and package.json declares "type": "module" with a CommonJS build under the require condition, so both import styles are covered by the exports map. The package also exposes ./standalone and ./jsx entry points, plus yoga.wasm as a file.

The basic call takes a JSX element and an options object with width, height and fonts. Fonts must be supplied as data: the README says to use fs in Node.js or fetch to read the font as a Buffer or ArrayBuffer. In the example below, robotoArrayBuffer stands in for that data, exactly as the README names it.

jsx
// api.jsx
import satori from 'satori'

const svg = await satori(
  <div style={{ color: 'black' }}>hello, world</div>,
  {
    width: 600,
    height: 400,
    fonts: [
      {
        name: 'Roboto',
        data: robotoArrayBuffer,
        weight: 400,
        style: 'normal',
      },
    ],
  },
)

The return value is a string shaped like '<svg ...><path d="..." fill="black"></path></svg>', so the caller decides what to do with it. To get a PNG for a social card, the README directs you to Vercel's OG image generation docs rather than describing the conversion itself.

If your toolchain has no JSX transpiler, the README shows passing a React-element-like object with type, props.children and props.style directly. There is also an experimental JSX runtime, enabled per file through @jsxImportSource pragmas, that lets you skip installing React; the README says it will eventually autocomplete only the supported elements and properties.

Images, fonts and the parts that fail quietly

Two details in the README deserve attention before you build anything on top of Satori. First, img elements work, but width and height attributes are recommended. When background-image is used without a size, the image is stretched to fit the element. Second, the README suggests using base64 image data, a buffer or an ArrayBuffer as props.src when the SVG will later be converted to another format, so Satori does no extra I/O.

The failure mode is not a crash; it is a visual difference. Because Satori runs its own engine and does not guarantee a match with the browser, a layout that looks correct in a browser can come out shifted, clipped or wrapped differently in the SVG. The README does not document a diffing or validation workflow for catching that, and it does not describe rollback or fallback behavior. If your design depends on text wrapping precisely, that gap is the risk you are accepting.

There is also a versioning caveat in the repository itself: package.json carries the version 0.0.0-development.2, which is a development placeholder, while the releases listed for the project run to 0.33.4. Read the published release rather than the repository manifest when you pin a version.

When Satori is the wrong tool

Satori is a poor fit for anything that needs a real browser. The README rules out style tags, link and script resources, and interactive React APIs such as useState, useEffect and dangerouslySetInnerHTML. Elements are expected to be pure and stateless. If your card design pulls in a web font through a link tag, relies on cursor states, or uses an input, it will not render as written.

It is also the wrong choice for archiving or screenshotting arbitrary web pages. There is no CSS cascade from external stylesheets, no media queries mentioned, and no full box model. And if your output must be pixel-identical to a browser screenshot for review purposes, the README's own disclaimer about matching makes that goal unattainable through Satori alone.

The project's last push was on 2026-09-10, and the most recent release listed is 0.33.4 on 2026-08-24. The repository is not archived, so it is maintained, but the release cadence shown here is a set of patch versions rather than a stable 1.0, which is worth weighing if you need long-term API guarantees.

How Satori differs from a headless browser renderer

The obvious alternative is driving a headless browser such as Puppeteer or Playwright and screenshotting a page. The approaches are opposites. A headless browser gives you the complete CSS engine, external stylesheets, web fonts and JavaScript execution, at the cost of a large binary, slower cold starts and more memory per instance. Satori gives you a small dependency and a string return value, at the cost of a documented subset and no guarantee of a pixel match.

The decision follows from where the rendering happens. In a serverless function that fires once per social share, the browser's startup cost is paid on every request, which is why the README points at Vercel's OG image generation path instead. In a build step where you render a few hundred images and can afford a container, the browser's fidelity is usually worth more than Satori's speed. Satori also returns SVG, not PNG, so a pipeline that needs raster output still has a conversion step to add.

Licence and the cost of keeping up

Satori is licensed under MPL-2.0, a file-level copyleft licence. Modifications to files that are part of Satori must be made available under the same licence, while separate files you write can carry other terms. This is not legal advice; check with your own counsel if you plan to fork or patch the library. The repository does contain a patches/ directory, which suggests patched dependencies are part of the build, and that is worth reviewing before you vendor the source.

Upgrade cost is mostly about the supported CSS surface. The README links the list of supported HTML elements and preset styles in src/handler/presets.ts, and the package exports a ./standalone build alongside the main entry, so there is more than one surface to keep working. Since the published versions sit in the 0.33.x range, expect minor releases to adjust the subset. A practical check before upgrading is to render your existing card templates and compare the SVG output, because the library's own disclaimer means the type checker will not catch a layout change.

Editorial conclusion

Adopt Satori if you generate Open Graph images, social cards or other fixed-size artwork per request and want the output as an SVG string without running a browser. Do not adopt it if your markup depends on the full CSS box model, media queries, external stylesheets or interactive elements; the README states it supports only a static, visible subset. Before committing, verify that every element and property in your design appears in src/handler/presets.ts, that your fonts load as Buffer or ArrayBuffer, and that your text measurement expectations hold, since the README says the SVG is not guaranteed to match browser-rendered HTML exactly.

Frequently asked questions

How do I use vercel/satori to turn JSX into an SVG?

Import satori, call it with a JSX element and an options object containing width, height and fonts, and await the result. The README's example renders a div with inline color into a 600x400 SVG and returns an SVG string.

Does vercel/satori support the full CSS specification?

No. The README states it is not a complete CSS implementation and supports a subset covering common features, with display, position, size and CSS variables among the documented properties. Elements like input and properties like cursor are out of scope.

What fonts does vercel/satori need to render text?

Fonts must be passed in the options object as data, read with fs in Node.js or fetch as a Buffer or ArrayBuffer. The README's example provides a name, data, weight and style for each font.

Can vercel/satori render an SVG that matches a browser exactly?

The README says Satori does not guarantee that the SVG will 100 percent match browser-rendered HTML, because it implements its own layout engine based on the SVG 1.1 spec. Treat the output as an approximation within the documented subset.

Official sources

  1. License: MPL-2.0
  2. Project website
  3. README
  4. Releases
  5. vercel/satori on GitHub
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/vercel-satori.svg)](https://hysenlabs.com/projects/vercel-satori)
Community notes

Community notes