Takumi

From satori & next/og

Switch from satori or next/og to Takumi, or compare the three on layout, CSS, fonts, and output formats.

satori turns JSX into an SVG string. next/og pairs an older satori with resvg to return a PNG. Takumi renders the same JSX to images, SVG, and more.

To switch, follow Migrate from satori or Migrate from next/og. To compare first, read How Takumi differs and Features.

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 with the same values, and Image loading & emoji lists each set and where it comes from.

To draw emoji from a registered color font, set emoji: "from-font". See Emoji.

How Takumi differs

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, and 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.

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, which has these effects:

  • Renders leave the event loop free, and 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, and 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