Takumi vs. Satori
Compare Takumi with Satori and next/og, including CSS support, output formats, and migration steps.
satori pioneered OG images without a headless browser and powers next/og. It turns JSX into an SVG string, so a bitmap takes a pipeline: yoga computes layout in WebAssembly, satori emits SVG, and resvg or sharp rasterizes it. Takumi is one Rust engine that does the whole job: JSX in, encoded image out.
Start migration by replacing the import. ImageResponse provides a next/og-compatible API, but the renderers have different layout and font behavior. Keep explicit Flexbox styles and compare your templates before deploying.
Compare rendered output across providers at image-bench.kane.tw.
Features
Templates and styling
| Feature | satori / next/og | Takumi |
|---|---|---|
| Template input | JSX | JSX, HTML strings, node trees |
| Styling | Inline styles, tw prop | + <style> tags, stylesheets |
Selectors, ::before / ::after | ❌ | ✅ |
@media, @keyframes, @supports | ❌ | ✅ |
calc() | ❌ | ✅ |
Selector support covers class, id, descendant, :is(), ::before, and ::after. See Styling.
Layout and text
| Feature | satori / next/og | Takumi |
|---|---|---|
| Layout modes | Flexbox only | Flexbox, CSS Grid, block, inline, float |
z-index | ❌ paint order only | ✅ |
backdrop-filter, blend modes | ❌ | ✅ |
| RTL text | ❌ | ✅ |
Output
| Feature | satori / next/og | Takumi |
|---|---|---|
| Raster (PNG, JPEG, WebP, ICO) | ❌ needs resvg or sharp | ✅ direct |
| Vector SVG | ✅ | ✅ renderSvg |
| Animated (GIF, APNG, WebP) | ❌ | ✅ |
| Raw pixel frames (video pipelines) | ❌ | ✅ |
Fonts, emoji, runtime
| Feature | satori / next/og | Takumi |
|---|---|---|
| Default font | Geist 400 (next/og only) | ✅ Geist 300 to 800 |
| WOFF2 | ❌ | ✅ |
| Emoji providers | next/og only | ✅ built in |
| Runtime | Node, Edge, browser | + Cloudflare Workers, Rust crate |
ImageResponse API | ✅ Native | ✅ Compatible |
Layout defaults
Satori defaults to display: flex and requires an explicit supported display value on a <div> with multiple children. Takumi defaults a bare <div> to display: block. Keep display: flex on containers that need Flexbox layout.
Migrate from satori
renderSvg replaces satori(). It accepts JSX and returns an SVG string. The built-in Latin font makes a fonts entry optional.
import satori from "satori";
import { renderSvg } from "takumi-js";
const svg = await satori(<div style={{ display: "flex" }}>Hello</div>, {
const svg = await renderSvg(<div style={{ display: "flex" }}>Hello</div>, {
width: 1200,
height: 630,
fonts: [{ name: "Inter", data: interBytes, weight: 400, style: "normal" }],
});Skip the SVG step entirely when the goal is a bitmap. satori pipelines pair with resvg or sharp to rasterize; render returns encoded bytes in one call.
import { } from "takumi-js";
const = await (< ="w-full h-full flex items-center justify-center">Hello</>, {
: 1200,
: 630,
});Migrate from next/og
ImageResponse matches the next/og API, including the satori-compatible emoji option. Swap the import.
import { ImageResponse } from "next/og";
import { ImageResponse } from "takumi-js/response";
export function GET() {
return new ImageResponse(<div tw="w-full h-full flex items-center justify-center">Hello</div>, {
width: 1200,
height: 630,
});
}The Next.js integration guide covers opengraph-image.tsx and route handlers.
Fonts
satori throws without at least one fonts entry. Takumi ships a built-in last-resort font (Geist, Latin only, weights 300 to 800), so Latin text renders with zero setup. For anything else, googleFonts fetches families in one call, and fonts also accepts raw bytes, descriptors, or bare URL strings.
import { } from "takumi-js";
import { } from "takumi-js/helpers";
const = await (
< ={{ : "Noto Sans TC" }} ="w-full h-full flex text-7xl">
你好,匠
</>,
{
: 1200,
: 630,
: ([{ : "Noto Sans TC", : 700 }]),
},
);See Fonts for script routing and preloading with Renderer.
Emoji
next/og resolves emoji through satori's loadAdditionalAsset callback. Takumi's ImageResponse accepts the same emoji option: twemoji, blobmoji, noto, openmoji, fluent, and fluentFlat. See Load images.
Animation
satori renders one static frame. Takumi threads a time axis through the pipeline: CSS @keyframes, the animation shorthand, and Tailwind animation utilities resolve at render time, and renderAnimation samples the same tree across timestamps into GIF, APNG, or animated WebP.
Performance and memory
Satori generates an SVG string on the calling thread. For raster output, pass that string to a separate rasterizer. Takumi's native binding moves the whole pipeline off the event loop:
- Renders run on worker threads.
renderexecutes as an async task, so the event loop stays free, and concurrent renders read the font store without locking. - Animation frames render in parallel across a thread pool and stream into the encoder, so encoded animations do not need to retain every raw frame.
- Images decode at draw size. A large photo drawn in a small box retains pixels at the draw size, not the source size, and the decode cache reuses them across renders.
- Fonts download by coverage.
googleFontssplits each family into unicode-range subsets, and a render fetches only the subsets its codepoints hit.
See Performance and optimization for tuning.
Tradeoffs
Measured from a clean npm install (macOS arm64) and gzip of the published artifacts, takumi-js 2.2.0 against satori 0.26 and @vercel/og 0.11:
| Package | Install size (node_modules) | Edge bundle (gzip) |
|---|---|---|
satori + @resvg/resvg-js | 14 MB | — |
@vercel/og | 35 MB (16 MB is optional sharp) | ~0.7 MB (index.edge.js + resvg.wasm) |
takumi-js | 8.6 MB | 1.5 MB (takumi_wasm_bg.wasm) |
In these versions, the Takumi WebAssembly binary measured 3.7 MB raw and 1.5 MB gzipped, about twice the measured @vercel/og edge stack. Include download and compilation costs when measuring cold starts. On Node.js, Takumi uses a native binary. Check current package sizes against your deployment target before choosing a backend.
Used in production
Dcard, TanStack, Fumadocs, and Luma render share images with Takumi, and Nuxt OG Image ships it as a built-in renderer. More in the showcase.
Last updated on