All posts
April 12, 20264 min read

Why we did not build Sone on top of Puppeteer

A short writeup on the trade-offs between headless browsers and a first-party layout engine, and why those trade-offs add up at scale.

Every project that needs to generate documents from code eventually faces the same question: do you spin up a headless Chrome and render HTML, or do you build a first-party rendering pipeline?

For one-off PDF exports the answer is almost always headless Chrome. The browser already speaks HTML and CSS fluently; you write the document like a webpage, screenshot it, and ship it. Done.

At scale the calculus changes. Each Chrome instance costs hundreds of megabytes of RAM and takes 100ms+ to render even a simple page. CDP traffic adds latency. Cold-starts hurt serverless functions. Hosted services like Browserless cost real money. And every CSS feature you reach for has a chance of behaving slightly differently across versions.

Sone takes a different bet: build the layout engine ourselves, on top of yoga-layout (the same engine React Native uses) and skia-canvas (native Skia bindings for Node). The API surface is exactly what the engine implements — there is no missing-CSS-spec problem because there is no CSS.

Render times drop into the single-digit-millisecond range for images and tens of milliseconds for multi-page PDFs. Memory stays flat. The same node tree renders to PNG, JPG, WebP, PDF, and SVG with no special modes.

It is not the right answer for everything. If your document is going to be edited as HTML by humans, headless browsers still win. But for invoices, OG images, certificates, and reports — programs talking to programs — Sone is dramatically faster, cheaper, and more predictable.