Skip to content

kane50613/takumi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2,332 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Takumi

Takumi

A Rust rendering engine that turns JSX, HTML, and node trees into images. No headless browser required.

Render OpenGraph cards, animated GIFs, and video frames from Node.js, Cloudflare Workers, browsers, or any Rust application. Drop-in compatible with next/og.

npm version crates.io npm downloads license

Documentation · Playground · Showcase

Why Takumi

Takumi is a rendering pipeline built in Rust for one job: turning markup and CSS into pixels. It parses CSS, lays out the tree, shapes text, composites layers, and encodes the output inside a single binary. A headless-Chromium setup spends around 300 MB of RAM and a browser cold start on the same OG card; Takumi spends a function call.

One engine covers every deployment target. Node.js servers load the native binding, Cloudflare Workers and browsers load the WASM build, and Rust applications embed the takumi crate. Prebuilt binaries ship for macOS, Linux (glibc and musl), and Windows, on both x64 and ARM64.

The CSS support reaches past the usual OG-image subset: CSS Grid, ::before and ::after, :is() and :where() selectors, masks and clip-path, backdrop-filter, background-clip: text, conic gradients, RTL text, and Tailwind v4 utilities including arbitrary values.

Quick Start

bun i takumi-js

Static image

import { render } from "takumi-js";
import { writeFile } from "node:fs/promises";

const image = await render(
  <div tw="w-full h-full flex items-center justify-center bg-gradient-to-b from-blue-100 to-red-50">
    <h1 tw="text-6xl font-bold">Hello from Takumi</h1>
  </div>,
  { width: 1200, height: 630 },
);

await writeFile("./output.png", image);

API route (next/og-compatible)

import { ImageResponse } from "takumi-js/response";

export function GET() {
  return new ImageResponse(
    <div tw="w-full h-full flex items-center justify-center bg-gradient-to-b from-blue-100 to-red-50">
      <h1 tw="text-6xl font-bold">Hello from Takumi</h1>
    </div>,
    { width: 1200, height: 630 },
  );
}

Animated WebP

import { Renderer } from "takumi-js/node";
import { fromJsx } from "takumi-js/helpers/jsx";
import { writeFile } from "node:fs/promises";

const renderer = new Renderer();

const { node, stylesheets } = await fromJsx(
  <div tw="w-full h-full flex items-center justify-center">
    <div tw="w-32 h-32 bg-blue-500 animate-spin rounded-lg" />
  </div>,
);

const animation = await renderer.renderAnimation({
  width: 400,
  height: 400,
  fps: 30,
  format: "webp",
  stylesheets,
  scenes: [{ durationMs: 1000, node }],
});

await writeFile("./output.webp", animation);

Rust

cargo add takumi

Start from the Rust example.

Comparison

Feature next/og (Satori) Takumi
Runtime Node / Edge Node, Edge, CF Workers, Browser, Rust crate
Template input JSX / React JSX, HTML strings, JSON node trees from any language
Layout Flexbox Flexbox, CSS Grid, block, inline, float
Selectors Limited Complex selectors, :is(), :where(), ::before, ::after
backdrop-filter, blend modes
Animated output WebP / APNG / GIF / video frames
Headless browser
ImageResponse API ✅ Native Compatible

Compare rendering output across providers at image-bench.kane.tw.

Who's Using Takumi

More projects in the showcase. Takumi is part of the Vercel OSS Program.

Core Architecture

Takumi converts any template into a node tree with three node kinds: container, image, and text. That tree runs through:

  1. Layout via taffy: Flexbox, Grid, block, float, calc(), absolute positioning, z-index
  2. Text shaping via parley and skrifa: WOFF/WOFF2 fonts, emoji, RTL, multi-span inline blocks
  3. Compositing: stacking contexts, blend modes, filters, transforms, SVG via resvg
  4. Output: PNG, JPEG, WebP, ICO for statics; GIF, APNG, WebP for animations; raw RGBA frames for video pipelines

The input contract is a node tree, so any template system that serializes to HTML or JSON can feed it: React, Svelte, Vue, plain strings, or your own serializer in any language.

A time axis threads through the pipeline: the renderer takes a timestamp, so a PNG is the tree at t=0 and a GIF is the same tree sampled across t. CSS @keyframes, the animation shorthand, and Tailwind animation utilities (animate-spin, animate-bounce, arbitrary values) all resolve at render time.

flowchart LR
    A[Templates] --> N[Node Tree] --> P[Rendering Pipeline] --> F[(Raw Pixels)]
    C[Stylesheets] --> P
    R[Resources] --> P
    D(Time Axis) -.-> P

    F --> G[PNG / JPEG / WebP / ICO]
    F --> H[GIF / APNG]
    F --> I[Video frames]
Loading

Showcase

Takumi OG image (source) Package OG card (source)
Takumi OG Image Package OG Image
Prisma-style API card (source) X-style social post (source)
Prisma OG Image X-style Post Image
Keyframe Animation (source) shiki-image
Keyframe Animation Shiki Image Example

More examples: Next.js, Cloudflare Workers, TanStack Start, Svelte, Rust, ffmpeg keyframe animation

Contributing

Read CONTRIBUTING.md. Covers local setup, test commands, fixture workflow, and changeset process.

We welcome bug reports, feature requests, doc improvements, and new example integrations.

License

MIT or Apache-2.0


Vercel OSS Program

About

Render JSX, HTML, and CSS to images without a headless browser. OG cards, animated GIFs, and video frames from Node.js, edge runtimes, browsers, or Rust. Drop-in next/og replacement.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Code of conduct

Contributing

Stars

Watchers

Forks

Sponsor this project

Contributors