Takumi vs. Satori
Compare Takumi with satori and next/og, including layout, CSS, fonts, output formats, and migration steps.
satori turns JSX into an SVG string. next/og pairs an older satori with resvg to return a PNG.
Takumi is one Rust engine. Its text and inline layout port Chromium's Blink, and the same layout feeds three packages:
| Package | Output |
|---|---|
takumi-js | PNG, JPEG, WebP, ICO, raw pixels, SVG, and animated WebP, APNG, or GIF |
takumi-pdf | Paged PDF with selectable text |
takumi-paint | A paint tree to draw with Canvas, slides, or a native API |
Compare rendered output across renderers at image-bench.kane.tw.
Features
satori changes quickly, so this table names versions. next/og in Next.js 16.4 bundles @vercel/og 1.0.1, which uses satori 0.29. The satori column follows the satori 0.63 README.
| Feature | next/og | satori 0.63 | Takumi |
|---|---|---|---|
| Layout | Flexbox only (Yoga) | Flexbox, Grid, block (Taffy), own inline layout | Flexbox, Grid, block, float, tables; inline layout from Blink |
display of a bare <div> | flex | block | block |
| Tables | ❌ | ❌ throws | ✅ Tables |
<style>, selectors, @media | ❌ | ✅ | ✅ |
calc(), z-index | ❌ | ✅ | ✅ |
@keyframes and animated output | ❌ | ❌ @keyframes is ignored | ✅ Keyframe animation |
React hooks (useState, use()) | ❌ | ❌ | ✅ initial state, no effects |
| WOFF2 | ❌ | ✅ | ✅ |
| Right-to-left text | ❌ | Partial | ✅ |
| Built-in font | Geist Regular | None, fonts is required | Geist, Latin, weights 300 to 800 |
| Output | PNG | SVG | Raster, SVG, animation, PDF, paint tree |
Takumi also takes HTML strings and node trees as input. See Parsing templates.
Migrate from satori
renderSvg replaces satori(). It takes JSX and returns an SVG string. The built-in Latin font makes fonts 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" }],
});For a bitmap, skip the SVG step. 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 follows the next/og API, including the 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,
});
}next/og makes every element a flex container. In Takumi, a <div> is display: block, as in a browser. Add display: flex (or tw="flex") to each container that needs Flexbox. Compare the output before you deploy.
The Next.js guide covers the route handler setup.
Fonts
satori throws without a fonts entry. Takumi ships Geist (Latin, weights 300 to 800) as a last-resort font, so Latin text renders with no setup.
For other scripts, googleFonts loads families in one call. fonts also takes raw bytes, descriptors, or 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 }]),
},
);satori 0.63 draws a variable font at its default instance. Takumi applies font-weight and font-variation-settings along the font's axes. See Fonts.
Emoji
next/og resolves emoji through satori's loadAdditionalAsset callback. Takumi's ImageResponse and render take the same emoji option: twemoji (the default), blobmoji, noto, openmoji, fluent, or fluentFlat. To draw emoji from a registered color font, set emoji: "from-font". See Emoji.
Rendering on a server
satori builds the SVG on the calling thread. On Node.js, Takumi's native binding runs each render as a background task:
- Renders leave the event loop free. Concurrent renders share the loaded fonts.
- Animation frames render in parallel and stream into the encoder, so it does not hold every raw frame.
- Images decode at their drawn size where the format allows it. The decode cache reuses them across renders.
- Fonts load by coverage.
googleFontsreturns each family asunicode-rangesubsets, and a render downloads only the subsets its text uses.
Edge runtimes and browsers load Takumi as a WebAssembly binary. Count its download and compile time when you measure cold starts. See Performance for tuning.
Used in production
Dcard, TanStack, Fumadocs, and Luma render share images with Takumi. Nuxt OG Image ships it as a renderer. See more in the showcase.
Last updated on