Render OG images and paged PDFs from JSX, HTML, and CSS. No headless browser. Runs on Node.js, Cloudflare Workers, browsers, and Rust.
2,968
stars
3,098
commits
Rust
primary language
Sep 11, 2026
updated
Render OG images and paged PDFs from JSX, HTML, and CSS. No headless browser.
Takumi renders images in Node.js, Cloudflare Workers, browsers, and Rust applications. The PDF package runs in Node.js, Bun, and Cloudflare Workers.
bun i takumi-js # PNG, JPEG, WebP, SVG, animations
bun i takumi-pdf # paged PDF
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);
import { render } from "takumi-pdf";
import { PageNumber, TotalPages } from "takumi-pdf/primitives";
import { writeFile } from "node:fs/promises";
const pdf = await render(<Invoice data={data} />, {
size: "a4",
footer: (
<div tw="flex w-full justify-center text-[10px] text-gray-500">
Page <PageNumber /> of <TotalPages />
</div>
),
});
await writeFile("invoice.pdf", pdf);
Takumi is a Rust rendering engine for markup and CSS. It handles layout, text shaping, compositing, and encoding without launching a browser. One component tree renders as an image, an animation, or a paged PDF.
text-fit grows or shrinks a headline to fill its line box, no measuring loop.text-wrap: balance evens multi-line headlines. pretty spares the orphan word.@keyframes and animate-spin become WebP, APNG, GIF, or video frames.var(), calc(), and media queries.lang picks each language's own Han glyphs for the same code points.offset-path. background-clip: text and conic gradients paint it.clip-path, backdrop-filter, and blend modes composite the way browsers do.filter: url(...), feTurbulence and feDisplacementMap included.corner-shape swaps round corners for squircle, bevel, scoop, notch, or superellipse(n).break-before: page, break-after: page, and break-inside: avoid.<PageNumber /> and <TotalPages /> count in CSS counter styles, trad-chinese-informal included.<thead> on every page.outline: true builds bookmarks from headings.takumi crate.| You are using | What changes |
|---|---|
satori | Replace satori() with renderSvg(), or call render() for encoded image bytes. Compare the renderers. |
next/og | Swap the ImageResponse import. Existing Satori-compatible templates keep their explicit Flexbox styles. Read the migration guide. |
| Puppeteer or Playwright for PDFs | Replace page.pdf() with render(). You must preload remote assets, and Takumi supports less CSS than Chrome. Read the migration guide. |
@react-pdf/renderer | Replace Document, View, and Text with HTML elements and CSS. Browser viewers and some text controls have no equivalent. Read the migration guide. |
pdfkit | Use JSX or HTML instead of positioning each line. Keep pdfkit when you need low-level drawing control. |
Only a last-resort Latin font ships built in. Load the rest through fonts: a URL, raw bytes, or googleFonts. A weight range or an axes entry loads the variable font, so font-variation-settings drives its axes:
import { render } from "takumi-js";
import { googleFonts } from "takumi-js/helpers";
const image = await render(
<div
tw="w-full h-full flex items-center justify-center"
style={{
fontSize: 72,
fontFamily: "Fraunces",
fontVariationSettings: "'opsz' 72, 'wght' 700",
}}
>
Hello from Takumi
</div>,
{
width: 1200,
height: 630,
fonts: googleFonts([{ name: "Fraunces", weight: "100..900", axes: { opsz: "9..144" } }]),
},
);
Rendering many images? Register the fonts once on a Renderer and reuse it. See Typography & Fonts.
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 },
);
}
import { renderAnimation } from "takumi-js";
import { writeFile } from "node:fs/promises";
const animation = await renderAnimation({
width: 400,
height: 400,
fps: 30,
format: "webp",
scenes: [
{
durationMs: 1000,
node: (
<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>
),
},
],
});
await writeFile("./output.webp", animation);
import { renderSvg } from "takumi-js";
import { writeFile } from "node:fs/promises";
const svg = await renderSvg(
<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.svg", svg);
cargo add takumi
Start from the Rust example.
| 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 |
| Vector SVG output | ✅ Native | ✅ Plus raster and animated output |
| Headless browser | ✗ | ✗ |
ImageResponse API | ✅ Native | ✅ Compatible |
Compare rendering output across providers at image-bench.kane.tw.
The benchmark uses an 80-line invoice with two pages and a page-number footer. Warm figures are the median of 20 renders. The environment was an Apple M1 Pro with macOS 15.7.4, Bun 1.3.14, and Chrome 151. The bench source reproduces the table.
| takumi-pdf 0.4 | @react-pdf/renderer 4.5.1 | Puppeteer + Chrome | |
|---|---|---|---|
| Cold start to first PDF | 176 ms | 495 ms | 0.7 to 2.8 s |
| Warm render (median) | 26 ms | 236 ms | 198 ms |
| Output size | 19 KB (15 KB with tagged: false) | 16 KB | 52 KB |
| Deploy needs | 1.5 MB gzip wasm | pure JS | Chrome install (hundreds of MB) |
| Template language | JSX, HTML, node trees with CSS and Tailwind | its own primitives (<View>, <Text>, StyleSheet) | HTML with full CSS |
| Selectable text, subset fonts | yes | yes | yes |
| Arabic and bidi text | yes | shaping only, manual direction | yes |
| Runs on edge runtimes | yes (Cloudflare Workers) | no (Node) | no |
Chrome has the most complete CSS support of the three. Takumi's PDF output does not support filter: blur(), drop-shadow(), or backdrop-filter. Use Puppeteer when you need to reproduce a complex web page pixel for pixel. See the PDF comparison for install sizes and more caveats.
More projects in the showcase. Takumi is part of the Vercel OSS Program.
Takumi converts any template into a node tree with three node kinds: container, image, and text. That tree runs through:
calc(), absolute positioning, z-indext=0. A GIF samples the same tree across t. CSS @keyframes, the animation shorthand, and Tailwind animation utilities resolve at render time.renderSvg() for Satori-style SVG, takumi-pdf for paged PDF.flowchart LR
A[Templates] --> N[Node Tree] --> P[Rendering Pipeline]
C[Stylesheets] --> P
R[Resources] --> P
D(Time Axis) -.-> P
P --> F[(Raw Pixels)]
P --> S[Vector SVG]
P --> V[Paged PDF]
F --> G[PNG / JPEG / WebP / ICO]
F --> H[GIF / APNG]
F --> I[Video frames]
| Takumi OG image (source) | Package OG card (source) |
|---|---|
![]() | ![]() |
| Prisma-style API card (source) | X-style social post (source) |
![]() | ![]() |
| Keyframe Animation (source) | shiki-image |
![]() |
See more examples for invoices, e-invoices, Next.js, Cloudflare Workers, TanStack Start, Svelte, Rust, and ffmpeg keyframe animation.
Read CONTRIBUTING.md for local setup and the fixture workflow.
We welcome bug reports, feature requests, doc improvements, and new example integrations.
MIT or Apache-2.0
Rust
81.2%
HTML
9.3%
TypeScript
9.3%
Render OG images and paged PDFs from JSX, HTML, and CSS. No headless browser. Runs on Node.js, Cloudflare Workers, browsers, and Rust.
2,968
stars
3,098
commits
Rust
primary language
Sep 11, 2026
updated
Render OG images and paged PDFs from JSX, HTML, and CSS. No headless browser.
Takumi renders images in Node.js, Cloudflare Workers, browsers, and Rust applications. The PDF package runs in Node.js, Bun, and Cloudflare Workers.
bun i takumi-js # PNG, JPEG, WebP, SVG, animations
bun i takumi-pdf # paged PDF
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);
import { render } from "takumi-pdf";
import { PageNumber, TotalPages } from "takumi-pdf/primitives";
import { writeFile } from "node:fs/promises";
const pdf = await render(<Invoice data={data} />, {
size: "a4",
footer: (
<div tw="flex w-full justify-center text-[10px] text-gray-500">
Page <PageNumber /> of <TotalPages />
</div>
),
});
await writeFile("invoice.pdf", pdf);
Takumi is a Rust rendering engine for markup and CSS. It handles layout, text shaping, compositing, and encoding without launching a browser. One component tree renders as an image, an animation, or a paged PDF.
text-fit grows or shrinks a headline to fill its line box, no measuring loop.text-wrap: balance evens multi-line headlines. pretty spares the orphan word.@keyframes and animate-spin become WebP, APNG, GIF, or video frames.var(), calc(), and media queries.lang picks each language's own Han glyphs for the same code points.offset-path. background-clip: text and conic gradients paint it.clip-path, backdrop-filter, and blend modes composite the way browsers do.filter: url(...), feTurbulence and feDisplacementMap included.corner-shape swaps round corners for squircle, bevel, scoop, notch, or superellipse(n).break-before: page, break-after: page, and break-inside: avoid.<PageNumber /> and <TotalPages /> count in CSS counter styles, trad-chinese-informal included.<thead> on every page.outline: true builds bookmarks from headings.takumi crate.| You are using | What changes |
|---|---|
satori | Replace satori() with renderSvg(), or call render() for encoded image bytes. Compare the renderers. |
next/og | Swap the ImageResponse import. Existing Satori-compatible templates keep their explicit Flexbox styles. Read the migration guide. |
| Puppeteer or Playwright for PDFs | Replace page.pdf() with render(). You must preload remote assets, and Takumi supports less CSS than Chrome. Read the migration guide. |
@react-pdf/renderer | Replace Document, View, and Text with HTML elements and CSS. Browser viewers and some text controls have no equivalent. Read the migration guide. |
pdfkit | Use JSX or HTML instead of positioning each line. Keep pdfkit when you need low-level drawing control. |
Only a last-resort Latin font ships built in. Load the rest through fonts: a URL, raw bytes, or googleFonts. A weight range or an axes entry loads the variable font, so font-variation-settings drives its axes:
import { render } from "takumi-js";
import { googleFonts } from "takumi-js/helpers";
const image = await render(
<div
tw="w-full h-full flex items-center justify-center"
style={{
fontSize: 72,
fontFamily: "Fraunces",
fontVariationSettings: "'opsz' 72, 'wght' 700",
}}
>
Hello from Takumi
</div>,
{
width: 1200,
height: 630,
fonts: googleFonts([{ name: "Fraunces", weight: "100..900", axes: { opsz: "9..144" } }]),
},
);
Rendering many images? Register the fonts once on a Renderer and reuse it. See Typography & Fonts.
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 },
);
}
import { renderAnimation } from "takumi-js";
import { writeFile } from "node:fs/promises";
const animation = await renderAnimation({
width: 400,
height: 400,
fps: 30,
format: "webp",
scenes: [
{
durationMs: 1000,
node: (
<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>
),
},
],
});
await writeFile("./output.webp", animation);
import { renderSvg } from "takumi-js";
import { writeFile } from "node:fs/promises";
const svg = await renderSvg(
<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.svg", svg);
cargo add takumi
Start from the Rust example.
| 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 |
| Vector SVG output | ✅ Native | ✅ Plus raster and animated output |
| Headless browser | ✗ | ✗ |
ImageResponse API | ✅ Native | ✅ Compatible |
Compare rendering output across providers at image-bench.kane.tw.
The benchmark uses an 80-line invoice with two pages and a page-number footer. Warm figures are the median of 20 renders. The environment was an Apple M1 Pro with macOS 15.7.4, Bun 1.3.14, and Chrome 151. The bench source reproduces the table.
| takumi-pdf 0.4 | @react-pdf/renderer 4.5.1 | Puppeteer + Chrome | |
|---|---|---|---|
| Cold start to first PDF | 176 ms | 495 ms | 0.7 to 2.8 s |
| Warm render (median) | 26 ms | 236 ms | 198 ms |
| Output size | 19 KB (15 KB with tagged: false) | 16 KB | 52 KB |
| Deploy needs | 1.5 MB gzip wasm | pure JS | Chrome install (hundreds of MB) |
| Template language | JSX, HTML, node trees with CSS and Tailwind | its own primitives (<View>, <Text>, StyleSheet) | HTML with full CSS |
| Selectable text, subset fonts | yes | yes | yes |
| Arabic and bidi text | yes | shaping only, manual direction | yes |
| Runs on edge runtimes | yes (Cloudflare Workers) | no (Node) | no |
Chrome has the most complete CSS support of the three. Takumi's PDF output does not support filter: blur(), drop-shadow(), or backdrop-filter. Use Puppeteer when you need to reproduce a complex web page pixel for pixel. See the PDF comparison for install sizes and more caveats.
More projects in the showcase. Takumi is part of the Vercel OSS Program.
Takumi converts any template into a node tree with three node kinds: container, image, and text. That tree runs through:
calc(), absolute positioning, z-indext=0. A GIF samples the same tree across t. CSS @keyframes, the animation shorthand, and Tailwind animation utilities resolve at render time.renderSvg() for Satori-style SVG, takumi-pdf for paged PDF.flowchart LR
A[Templates] --> N[Node Tree] --> P[Rendering Pipeline]
C[Stylesheets] --> P
R[Resources] --> P
D(Time Axis) -.-> P
P --> F[(Raw Pixels)]
P --> S[Vector SVG]
P --> V[Paged PDF]
F --> G[PNG / JPEG / WebP / ICO]
F --> H[GIF / APNG]
F --> I[Video frames]
| Takumi OG image (source) | Package OG card (source) |
|---|---|
![]() | ![]() |
| Prisma-style API card (source) | X-style social post (source) |
![]() | ![]() |
| Keyframe Animation (source) | shiki-image |
![]() |
See more examples for invoices, e-invoices, Next.js, Cloudflare Workers, TanStack Start, Svelte, Rust, and ffmpeg keyframe animation.
Read CONTRIBUTING.md for local setup and the fixture workflow.
We welcome bug reports, feature requests, doc improvements, and new example integrations.
MIT or Apache-2.0
Rust
81.2%
HTML
9.3%
TypeScript
9.3%