Why We Built a Rust PDF Engine Instead of Using Puppeteer
The technical story behind pdfRelay's rendering engine: why Chromium is overkill for PDF generation and how Rust solves the problem.
The obvious choice
When we started building pdfRelay, Puppeteer was the obvious choice. It's free, well-documented, and renders HTML exactly like Chrome. Everyone uses it for PDF generation. Why build something from scratch?
The problems we hit
Our first prototype used Puppeteer. Within a week we hit every problem our customers eventually complain about:
- 300MB Docker images — Chromium is enormous. Our deploy times tripled.
- 2-5 second cold starts — every first request after idle waited for Chrome to launch.
- Memory pressure — 10 concurrent conversions needed 3GB+ of RAM.
- Zombie processes — crashed conversions left orphaned Chrome processes that slowly ate all memory.
- Font inconsistency — PDFs looked different on macOS vs Linux vs CI.
Why Rust?
We needed a rendering engine that:
- Parses HTML and CSS without a browser
- Produces consistent output regardless of platform
- Starts instantly (no browser launch)
- Uses minimal memory (no DOM, no JavaScript engine, no GPU compositor)
- Can handle thousands of conversions per second
Rust gave us all of this. Our engine parses HTML (html5ever), resolves CSS (lightningcss), lays out content (custom layout engine with flexbox/grid via taffy), shapes text (rustybuzz/cosmic-text), and generates PDF (krilla). The entire pipeline runs in a single process with no external dependencies.
The result
A typical conversion takes 20-100ms — including CSS parsing, layout, text shaping, and PDF generation. That's 10-50x faster than Puppeteer. Memory usage is 10-50MB per conversion instead of 200-400MB. No Docker images with Chromium. No zombie processes. No font inconsistencies.
And the best part: your templates are just HTML and CSS. The same web standards every developer already knows. No proprietary markup, no coordinate-based positioning, no new language to learn.