Social cards & OG images

Satori

Vercel's open-source library that turns JSX and CSS into SVG — the engine behind dynamic Open Graph images

vercel/satoriTypeScript13,746 as of 2026-08-07
By Jake Luo · Published 2026年8月7日

Satori is an open-source library from Vercel that renders JSX and a subset of CSS into an SVG, which you then rasterize to PNG. It is the engine underneath Vercel's Open Graph image generation and Next.js's ImageResponse, so most people who generate social cards are using it without naming it. For a founder it solves one narrow distribution problem properly: every link you share, and every link somebody else shares, carries a preview image — Satori lets that image be generated from your data instead of designed by hand, one page at a time.

What Satori is

Satori (github.com/vercel/satori) takes a JSX element and returns an SVG string. It is written in TypeScript and licensed MPL-2.0. You hand it a tree of plain elements plus a font as a buffer, it lays the tree out with Yoga — the same flexbox engine React Native uses — and it hands back vector output that you convert to a PNG. It runs in the browser, in Node 16 and above, and in Web Workers, bundling its WebAssembly dependencies inline.

It is deliberately not a browser, and the constraints are the interesting part. The JSX must be pure and stateless: no React hooks, no dangerouslySetInnerHTML. There are no style tags and no external stylesheets or scripts — every style is inline. CSS support is a documented subset, `display` defaults to `flex` and only accepts `flex`, `contents` or `none`, and the project is explicit that the output is not promised to match a browser pixel for pixel. Most people never touch the library directly: they use `next/og` or Vercel's OG image generation, which wrap Satori with a rasterizer and an HTTP response. There is also a live playground at og-playground.vercel.app that is the fastest way to find out whether a layout is expressible.

Where a preview image actually earns its keep

A social card is the only creative asset that travels with a link automatically. You do not choose where it appears, and the surfaces where it matters most are the ones you can see least:

  • Private shares A link pasted into Slack, WhatsApp or a DM unfurls into a card, and that card is the entire pitch for a reader who has never heard of you. This traffic arrives with no referrer, so it lands in Direct — the dark social gap — which means the card is doing work you will never see attributed.
  • Per-page, not per-site One hand-made card for the whole domain means every page of yours looks identical in a feed. Generated cards let a comparison page, a changelog entry and a docs page each show their own title, which is the difference between a link that says "a website" and one that says "the answer you asked for".
  • Programmatic pages If you publish templated pages at any scale — programmatic SEO, directory entries, per-integration landing pages — hand-designing cards stops being possible at about page thirty. Generation is the only version of this that survives contact with a large page set.

Worth being honest about the ceiling: `og:image` is not a ranking signal, and no card rescues a page nobody wants to share. It changes the click-through on shares that were already going to happen, which is a real but bounded effect — closer in size to a good title tag than to a new channel.

What we learned shipping our own card

We generate the social card for AgentCeres — the AI Growth Officer at agentceres.com — with `next/og`, which is Satori underneath, and two things cost us more than the card itself. The first was a routing mistake with no error attached: the convention file sat one segment above the layout every page actually hangs off, so it emitted no image at all and the route 404'd, silently, in production until we found it on 14 July 2026. Nothing warns you — a missing preview image looks exactly like a preview image you have not looked at, and you only notice when you paste your own link somewhere and it comes back naked. If you generate cards, add a check that fetches the image route and asserts a 200.

The second was cheaper but universal: Satori's layout rules are not browser rules. Every wrapper element in our card carries an explicit `display: flex`, the brand mark is inlined as a base64 data URI rather than fetched, and the card is prerendered per locale so it is a static asset instead of an edge render on every crawl. None of that is exotic — it is the shape a Satori template converges on once you stop trying to write CSS you would write for a page. The tool is fifteen minutes of work; deciding what a stranger should see when your link appears in someone else's chat window is the part worth thinking about.

FAQ

Is Satori the same thing as @vercel/og?
No. Satori is the layout and render library that turns JSX into SVG. @vercel/og — and Next.js's ImageResponse, exported from next/og — wraps it with a rasterizer that converts the SVG to PNG plus a cache-friendly HTTP response. Most applications want the wrapper. You reach for Satori directly when you want SVG output, or when you are working outside Next.js.
Do dynamic Open Graph images help SEO?
Not directly — og:image is not a ranking signal, and Google does not rank pages higher for having a nicer preview. The effect is on distribution: a card determines whether a shared link gets clicked in a feed or a chat. Judge it as share click-through, not as search performance.
What are the main limitations?
CSS support is a documented subset laid out by Yoga's flexbox engine, so block and grid layouts do not behave the way they do in a browser; style tags and external stylesheets are unsupported; fonts must be supplied as buffers; the JSX has to be pure and stateless; and the project states plainly that output is not promised to match browser rendering exactly. Rendering also gets slower as you load more fonts and emoji per image.
Do I have to generate cards at request time?
No, and often you should not. If your pages are known at build time, prerender the images so each one is a static file served from cache rather than a function invocation on every crawler request. We prerender ours per locale for exactly that reason. Request-time generation is the right call when the card reflects data that changes — a live number, a user's name, a dated report.
Related projects
Next SEOMIT-licensed React components for structured data in Next.js projectsDubOpen-source link management and attribution for modern marketing teamsWebstudioOpen-source visual website builder — a Webflow alternative you can host yourself

You built it. Now grow it.

AgentCeres is a managed AI marketing team — specialists draft the SEO, social, and outreach that fill your links, you approve what ships. 14-day free trial, from $39/month.

Start free trialMore projects