The HTML-to-PDF problem
If you've ever generated PDFs in production, you know the pattern. You write an HTML template, style it with CSS, spin up a headless browser, call page.pdf(), and pray that the output matches what you saw in your browser's dev tools. Sometimes it does. Often it doesn't.
The root cause is a fundamental mismatch: HTML and CSS were designed for scrolling screens, not fixed-size pages. Every HTML-to-PDF tool is essentially a hack — a screen renderer forced to simulate a printer. This works well enough for simple cases, but the moment your documents need reliable pagination, precise measurements, or consistent output across environments, the cracks show.
The problems compound at scale. Headless Chrome consumes 200–400MB of RAM per instance. Rendering takes 2–5 seconds per document. CSS pagination rules (page-break-inside, orphans, widows) are implemented inconsistently across browser versions. A Chromium update can silently change your output. You're not generating documents — you're screenshotting a web page and hoping for the best.
What is Typst?
Typst is a compiled typesetting language created in 2023 by Martin Haug and Laurenz Mager, originally as a thesis project at TU Berlin. Think of it as a modern replacement for LaTeX: a language specifically designed to describe documents, not web pages.
Key characteristics that matter for PDF generation:
- Compiled, not interpreted: Typst source goes through a compiler that produces PDF directly. No browser. No DOM. No layout engine repurposed from a different context.
- Document-native primitives: Pages, margins, headers, footers, columns, page numbering, cross-references — these are first-class concepts, not CSS hacks.
- Written in Rust: The compiler is fast and memory-efficient. A single-page invoice compiles in 50–200ms using ~20MB of RAM.
- Deterministic output: Same source always produces the exact same PDF, regardless of OS, environment, or installed fonts (fonts are embedded).
Typst is often compared to LaTeX, but the developer experience is radically different. No package manager hell, no cryptic error messages, no 30-second compile times. Typst compiles incrementally in milliseconds and has clear, actionable error messages. If you've avoided LaTeX because of its complexity, Typst is worth a fresh look.
The 5 problems with HTML-to-PDF
1. Speed: headless browsers are slow
Generating a PDF with Puppeteer means launching (or connecting to) a Chromium instance, creating a new page, injecting HTML, waiting for network-idle, and calling the print-to-PDF function. Even with a warm browser pool, you're looking at 800ms–2,000ms per document. Cold starts push this to 4–5 seconds.
Typst compiles the same invoice in 50–200ms. There's no browser to launch, no DOM to construct, no layout engine to warm up. The compiler reads the source, runs the layout algorithm, and emits PDF bytes. This is a 10–20x improvement that directly impacts your API response times, your queue throughput, and your infrastructure costs.
| Metric | HTML-to-PDF (Puppeteer) | Typst |
|---|---|---|
| Cold start | 2,000–5,000ms | 50–200ms |
| Warm render | 800–2,000ms | 50–200ms |
| Memory per render | 200–400MB | 15–30MB |
| Concurrent renders (4GB RAM) | 10–15 | 100+ |
| Binary size | ~300MB (Chromium) | ~15MB |
2. CSS pagination is fundamentally broken
CSS was designed for continuous scroll, not discrete pages. The spec includes page-break-before, page-break-after, page-break-inside, orphans, and widows — but browser support is inconsistent and the behavior is often surprising.
Common failures in HTML-to-PDF pagination:
- Table rows split across pages with the header missing on page 2
page-break-inside: avoidsilently ignored when the element is taller than the page- Flex and Grid layouts breaking unpredictably across page boundaries
- Absolutely positioned elements overlapping page margins
- Footer content that should repeat on every page requiring JavaScript hacks
In Typst, pagination is a solved problem. The layout engine understands pages as a fundamental unit. Tables automatically repeat headers. Content flows across pages with predictable, controllable behavior. Repeating headers and footers are a single line of configuration, not a JavaScript workaround.
3. Memory consumption makes scaling expensive
Each Puppeteer instance runs a full Chromium process. In production, this means you need a browser pool, and each browser in that pool consumes 200–400MB of RAM — even when idle. A t3.medium instance (4GB RAM) can realistically handle 10–15 concurrent renders before you start swapping. You end up scaling vertically (bigger instances) instead of horizontally, which is both expensive and fragile.
Typst compiles documents in a single-threaded, stateless process that allocates ~20MB per render and releases it immediately. On the same t3.medium, you can handle 100+ concurrent renders. This is not a marginal improvement — it changes the economics of document generation entirely.
4. Reproducibility is not guaranteed
HTML-to-PDF output depends on the browser version, the operating system's font rendering stack, installed system fonts, and the specific combination of CSS features used. A Chromium update from 122 to 124 can change line heights, font metrics, or table layouts in ways that are invisible in a diff but visible on paper.
This is particularly painful for regulated industries (finance, healthcare, legal) where document output must be reproducible and auditable. "The PDF looks different because Chrome updated" is not an acceptable explanation when a regulator asks why your contract changed.
Typst embeds all fonts and uses its own layout engine. Same source, same output, every time. There is no external rendering dependency that can change between builds.
5. CSS is for screens, not paper
CSS lacks native concepts for things documents need: precise typographic control (kerning, ligatures, hanging punctuation), cross-references ("see page 5"), automatic table-of-contents generation, footnotes, margin notes, and running headers that change based on the current section. You can hack most of these with JavaScript and absolute positioning, but you're fighting the tool instead of using it.
Typst has all of these as built-in features. A footnote is #footnote[Your text here]. A cross-reference is @label. A running header is a function on the page template. These aren't libraries or plugins — they're part of the language.
How Typst solves each problem
| Problem | HTML-to-PDF approach | Typst approach |
|---|---|---|
| Speed | Browser pool, warm instances, queue workers | Compiled in <200ms, no pool needed |
| Pagination | CSS page-break rules (fragile) | Native page-aware layout engine |
| Memory | 200–400MB per Chromium instance | ~20MB per compile, stateless |
| Reproducibility | Depends on browser version + OS | Deterministic, fonts embedded |
| Document features | Hacked with JS + CSS tricks | Footnotes, TOC, refs built-in |
| Headers/footers | JS injection or absolute positioning | First-class page template concept |
Code comparison: the same invoice
Here's the same simple invoice generated with both approaches. Notice the difference in complexity and how each handles layout, pagination, and dynamic data.
The Typst version is shorter, more readable, and produces better output. But the real difference is what happens when the invoice has 200 line items and spans 8 pages. The HTML version will break rows across pages, lose table headers, and require JavaScript hacks to add page numbers. The Typst version handles all of this automatically.
When HTML-to-PDF still makes sense
Typst is not always the right answer. HTML-to-PDF tools have legitimate use cases:
Existing HTML content
If you're converting existing web pages, marketing emails, or HTML reports to PDF for archival purposes, using a browser-based tool is the path of least resistance. Re-authoring in Typst is unnecessary overhead.
JavaScript-dependent rendering
Documents that require JS-rendered charts (D3, Chart.js), interactive components captured as snapshots, or content loaded via AJAX need a real browser. Typst has no JavaScript runtime.
Large existing template libraries
If your team maintains 50+ HTML/CSS templates that work reliably, the migration cost to Typst may not justify the performance gains. Optimize incrementally — migrate high-volume templates first.
Web-to-PDF parity
When the PDF must look identical to a web page that users also view in a browser (e.g., a print stylesheet for a dashboard), browser-based rendering guarantees pixel-level parity.
If your use case is "data in, document out" (invoices, contracts, reports, certificates, pay slips, receipts), Typst is the better tool. If your use case is "web page to PDF," a browser-based tool is the better tool. Most production PDF generation falls into the first category.
How Typsetter uses Typst
Typsetter is a hosted API built on Typst. We handle the infrastructure so you don't have to compile Typst yourself, manage servers, or think about concurrency. Here's how it works:
- Template system: Templates are written in Typst with Tera (Jinja2-like) variables. You design once in the visual editor or code editor, then render by sending JSON data to the API.
- REST API: One
POST /v1/rendercall with your template slug and data object. You get PDF bytes back in ~340ms (network included). - Template store: 60+ pre-built templates (invoices, contracts, certificates, receipts) that you can use immediately or fork and customize.
- Batch generation: Upload a CSV, get a ZIP of PDFs. No loop required on your side.
- No infrastructure: No Chromium to install, no browser pool to manage, no Typst binary to update. The API handles compilation, font embedding, and caching.
The key difference between using Typsetter and running Typst yourself: you don't need to manage the Typst compiler, handle font loading, configure a compilation environment, or build the template injection layer. The API abstracts all of this behind a single HTTP call.
Conclusion: the right tool for the right job
HTML-to-PDF tools were built in an era when the browser was the only rendering engine capable of handling rich layouts. That era is over. Typst proves that a purpose-built document compiler can be faster, lighter, more reliable, and produce better typographic output than any browser-based approach.
The numbers speak for themselves:
- 10–20x faster render times (50–200ms vs 800–4,200ms)
- 10–20x less memory per render (~20MB vs 200–400MB)
- 100% reproducible output across environments
- Native pagination with repeating headers, footnotes, and page numbering
- Zero infrastructure when used through Typsetter's API
If you're still running Puppeteer or wkhtmltopdf for data-driven documents — invoices, contracts, reports, certificates — you're paying a browser tax for capabilities you don't need. Typst eliminates that tax entirely.
Try Typst-powered PDF generation
100 PDFs/month free. No Chromium required. API key in 30 seconds.