Comparison
takumi-pdf against Puppeteer and react-pdf, measured.
This comparison benchmarks three JavaScript PDF renderers. The benchmark uses an 80-line invoice. The invoice spans two pages and has a page-number footer.
Each harness uses its engine's native template language. Geometry, columns, and the embedded font (Inter 400/700) are the same in each harness. react-pdf's pt values mirror the other renderers' px values. takumi and Chrome produce the same two-page layout.
Warm figures are the median of 20 renders. The bench source reproduces this table. Run it with bun bench.
Environment: Apple M1 Pro, macOS 15.7.4, Bun 1.3.14, Chrome 151.
| takumi-pdf 0.4 | @react-pdf/renderer 4.5.1 | Puppeteer + Chrome | |
|---|---|---|---|
| Cold start to first PDF | 176 ms | 495 ms | 0.7–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 |
Bundle and install size
These measurements use npm-registry packages and esbuild (--bundle --minify, gzip). Install size comes from a clean bun add.
| JS bundle (min+gzip) | wasm (gzip) | node_modules | |
|---|---|---|---|
| takumi-pdf 0.4 | 10 KB | 1.52 MB (1.16 MB brotli) | 4.2 MB, 45 files |
| @react-pdf/renderer 4.5.1 | 493 KB | — | 32 MB, 1998 files |
| pdf-lib 1.17.1 | 179 KB | — | 26 MB |
| pdfkit 0.19.1 | 253 KB | — | 23 MB |
| jspdf 4.2.1 | 248 KB | — | 59 MB |
| Puppeteer + Chrome | — | — | Chrome install (hundreds of MB) |
pdf-lib, pdfkit, and jspdf are drawing primitives. You position each text line yourself. They are not in the benchmark above.
takumi-pdf's wasm binary is the largest single JS download. It is also the whole engine. node_modules has 45 files.
Cloudflare Workers caps a free-plan bundle at 3 MB after compression. The 1.52 MB wasm and the 10 KB JS wrapper leave half of that for application code.
Reading the numbers honestly:
- Chrome's cold start includes launching a browser process. It varies with machine state. Its memory spans several processes. A single-process measurement misses this. Chrome performs well after warming. It has the most complete CSS support of the three.
- react-pdf embeds the same Inter subsets as the other two. Subsetting on every render is most of its warm cost; the standard 14 PDF fonts avoid it. Documents use react-pdf's component set. Existing HTML, JSX, and Tailwind markup cannot be reused. It shapes Arabic, but paragraph direction is manual, and our Arabic sample dropped its first letter.
- takumi-pdf reuses OG-image components unchanged. Its CSS coverage is narrower than Chromium's. PDF output does not support
filter: blur(),drop-shadow(), orbackdrop-filter. A blurredbox-shadowis approximated with bands, and a blurredtext-shadowdraws sharp. Tagged output is on by default and adds about 4 KB to this invoice, down from 10 KB before the structure tree moved into an object stream.tagged: falseremoves it.
Rule of thumb: use Puppeteer to reproduce a complex web page pixel-perfectly. Any renderer suits documents written from scratch for PDF work. Use takumi-pdf to reuse Takumi image components. It suits edge runtimes. It also offers lower per-document latency.
Last updated on