Takumi

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:

PackageOutput
takumi-jsPNG, JPEG, WebP, ICO, raw pixels, SVG, and animated WebP, APNG, or GIF
takumi-pdfPaged PDF with selectable text
takumi-paintA paint tree to draw with Canvas, slides, or a native API
JSX satori SVG string resvg / sharp PNG JSX / HTML Takumi layout takumi-js: images, SVG, animations takumi-pdf: PDF takumi-paint: paint tree

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.

Featurenext/ogsatori 0.63Takumi
LayoutFlexbox only (Yoga)Flexbox, Grid, block (Taffy), own inline layoutFlexbox, Grid, block, float, tables; inline layout from Blink
display of a bare <div>flexblockblock
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 fontGeist RegularNone, fonts is requiredGeist, Latin, weights 300 to 800
OutputPNGSVGRaster, 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.

Replace satori with renderSvg
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.

Render PNG directly
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.

Replace the next/og 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.

Load a CJK font
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. googleFonts returns each family as unicode-range subsets, 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