Uptrack

April 18, 2026

Stop launching a browser to generate PDFs

At some point, almost every developer who needs to generate a PDF reaches for Puppeteer or wkhtmltopdf. The reasoning makes sense: you already know HTML and CSS, so why learn something new? Just render the page and screenshot it to PDF.

Then reality sets in. Puppeteer boots a full Chromium instance — 200–400ms just to initialize, before you've rendered a single character. wkhtmltopdf uses a frozen 2013 WebKit and breaks on modern CSS. Both are black boxes that produce subtly wrong output depending on font rendering, page size, and the phase of the moon.

For our invoice app 9invoice, we needed to generate hundreds of invoices on demand with pixel-perfect output. We tried the browser approach. Then we found Typst, and we've never looked back.

What is Typst?

Typst is a modern typesetting system — a LaTeX replacement built from scratch in Rust. You write documents in a scripting language that compiles directly to PDF. No browser. No WebKit. No XML. Just a fast, programmable engine that takes a template and data and outputs a perfectly formatted document.

Think of it as LaTeX with a sane syntax and compile times measured in milliseconds rather than minutes. Or think of it as "Markdown that can do everything Word can do, and exports to PDF natively."

Typst ships as a single binary. No runtime dependencies. No TeX distribution to install. No headless Chrome to keep alive. You call it, it outputs a PDF, it exits.

What's wrong with the traditional approaches

Puppeteer / headless Chrome: 400ms to do nothing

Chromium takes 200–400ms just to start up. Then you navigate to a URL or load HTML, wait for fonts and images, call page.pdf(), and wait again. For a single invoice on a beefy server, that's easily 700ms–1s. Under load with multiple concurrent requests, you're either queuing them (adding latency) or launching multiple Chromium instances (adding memory and CPU). A 2-core server struggles to render more than a few concurrent PDFs without falling over.

wkhtmltopdf: frozen in 2013

wkhtmltopdf uses a patched QtWebKit that was abandoned in 2015. It doesn't support flexbox properly, doesn't support CSS Grid at all, and has no active maintenance. Your carefully designed invoice template looks correct in every browser and wrong in wkhtmltopdf. You spend hours adding wkhtmltopdf-specific CSS hacks that break the browser preview. You ship a product that works, technically, but every update to your design requires re-testing against a decade-old rendering engine.

LaTeX: powerful but punishing

LaTeX produces beautiful documents. It's also a 40-year-old macro language with backslash syntax, cryptic error messages, and compile times that feel like the 1980s. A full TeX distribution is gigabytes of software. Generating a simple invoice means learning arcane packages, fighting with whitespace, and debugging errors like ! Extra }, or forgotten \endgroup. LaTeX is great for academic papers. It's overkill and undersupported for programmatic document generation.

Why Typst wins for programmatic PDF generation

Millisecond compile times

Typst compiles a typical invoice template to PDF in 5–30ms. Not per page. Total. That's 30–100x faster than the Puppeteer approach. On a single server core, you can generate thousands of PDFs per minute. Concurrency stops being a problem because each generation is so fast that requests queue for microseconds, not seconds.

A scripting language built for documents

Typst has real programming constructs — variables, loops, functions, conditionals — built into the template language. You can loop over line items, conditionally show tax sections, format currencies, and compute totals, all inside the template. No need to pre-process data in your backend and inject pre-formatted strings. The template is self-contained logic, not a dumb string interpolation target.

Consistent, pixel-perfect output

Typst doesn't use a browser engine. It has its own layout engine written in Rust with deterministic behavior. The PDF it outputs on your MacBook is identical to the one it outputs on a Linux server or a Windows CI runner. No font rendering differences. No mysterious extra blank pages. No margin quirks depending on the screen DPI. What you design is exactly what ships.

Templates are plain text — version control them

A Typst template is a .typ file. Plain text. You commit it to Git, review changes in diffs, roll back when something breaks. Compare this to maintaining an invoice template in Word, or in a WYSIWYG editor inside some SaaS tool. With Typst, your template is code. It has the same review and deployment workflow as everything else.

Single binary, zero runtime dependencies

Typst ships as a single self-contained binary. You add it to your server and call it as a subprocess. No npm packages to keep updated. No Chromium version drift. No TeX distribution to install. No Python dependency for WeasyPrint. Your Docker image stays small. Your deployment stays predictable.

How we use it in 9invoice

In 9invoice, every invoice PDF is generated by calling the Typst CLI as a subprocess from our backend. The workflow looks like this:

1. Template with injected data

We maintain a invoice.typ template that reads invoice data from a JSON file passed as an input. The template handles line item loops, currency formatting, conditional tax display, and multi-page layout automatically. Changing the invoice design means editing one plain-text file.

2. Subprocess call from the backend

When a user requests an invoice PDF, our server writes the invoice data to a temp file and calls typst compile invoice.typ output.pdf. The process exits in under 30ms. We stream the resulting PDF directly to the response. No S3 upload. No async job queue. The PDF is ready before the user's browser has finished processing the click.

3. Fonts bundled with the binary

Typst embeds fonts directly into the PDF so it looks the same everywhere. We embed Inter for UI text and a monospace font for invoice numbers. No relying on system fonts that vary between machines. No worrying about fonts missing in the Docker container. The PDF is a self-contained artifact.

When Typst might not be the right choice

Typst is not a drop-in replacement for every PDF use case.

If you need to convert an existing HTML page to PDF — a webpage, a marketing email, a web-designed report — Puppeteer is still the right tool. Typst has its own layout model; it doesn't understand CSS. You can't take an existing HTML template and run it through Typst without rewriting it.

If your team already has LaTeX expertise and is producing academic papers, Typst is a worthy upgrade but not urgent. LaTeX's ecosystem of packages (especially for math) is broader.

But if you're building document generation into a product — invoices, receipts, reports, contracts, certificates — and you're currently launching a browser to do it, Typst is worth two hours of evaluation. The speed difference alone will surprise you.

Getting started in 15 minutes

Install Typst with a single command:

# macOS
brew install typst

# Linux / Windows
# Download from https://github.com/typst/typst/releases

Write a minimal invoice template:

// invoice.typ
#let data = json("data.json")

#set page(paper: "a4", margin: 2cm)
#set text(font: "Inter", size: 11pt)

= Invoice #data.number

*Bill to:* #data.client.name

#table(
  columns: (1fr, auto, auto),
  [Description], [Qty], [Amount],
  ..data.items.map(i => (
    i.description,
    str(i.qty),
    "$" + str(i.amount),
  )).flatten()
)

*Total: $#data.total*

Compile it:

typst compile invoice.typ

That's it. A PDF appears in under 30ms. No browser. No LaTeX. No XML. From there, call it as a subprocess from any language, pass in real data, and you have production PDF generation.

Keep tabs on what matters

While Typst keeps your PDFs fast, Uptrack keeps your services up. 50 free monitors — 10 at 30-second checks, 40 at 1-minute.

Start Monitoring Free