Why PageRush renders PDFs with Typst instead of a headless browser
Most PDF APIs run Chrome behind the scenes. We measured what a typesetting compiler buys instead, from a 4 ms invoice to a 300-page report in under four seconds.
- typst
- architecture
- performance
Almost every hosted PDF API works the same way: you send HTML and CSS, a headless Chrome instance loads it, and the browser's print pipeline writes a PDF. It is a reasonable default. Developers already know HTML, and the output looks like the page did on screen.
PageRush does something different. Templates are written in Typst, a typesetting language with a real compiler, and rendered by that compiler running natively on a server with no browser anywhere in the path. This post explains why, with the numbers that convinced us.
What a browser costs you
A browser is a general-purpose rendering engine. To print a two-page invoice it has to start a process, parse HTML and CSS, build a layout tree, run any JavaScript, wait for fonts and images, paint, and then hand the result to a print backend that was designed for a person pressing Ctrl+P. Each of those steps was built for something other than producing documents.
The costs show up in three places:
- Time. A browser has to start, load the page and everything it references, lay it out and print it before there is a PDF. Providers keep pools of warm browsers to hide the start-up, which is expensive and still leaks through under load. We measured Chrome against Typst on the same document below.
- Memory. A browser tab holds a full DOM, a layout tree, a compositor and a JavaScript heap. Long documents multiply all of them. Hosting one browser per concurrent render is the reason headless PDF services cost what they do.
- Pagination. CSS was designed for scrolling, not pages. Repeating table headers across pages, keeping a heading with the paragraph after it, running page numbers, and "page 3 of 12" footers range from fiddly to impossible, and behave differently across browser versions.
What a typesetting compiler does instead
Typst is a document compiler in the lineage of LaTeX, but with a modern language and a fast implementation in Rust. It reads a source file, lays out pages directly, and writes a PDF. There is no DOM, no event loop, and no printing step. Pages, headers, footers, footnotes and repeating table headers are first-class concepts, because producing paged documents is the only thing it does.
For an API, the important property is that compiling is close to a pure function. The same template and the same data produce the same document, unless the template asks for something that changes, such as today's date or an image loaded from an address. We rely on this for immutable template versions: a job records the version it rendered, and that version renders the same document from the same data later.
The measurements
These were taken on 3 November 2026 on the PageRush compile service in Mumbai, which runs Typst 0.15.1 on an arm64 server with 2 GB of memory. Each document was rendered with different data on each run, and the times are the billed compute time for the render alone, without network.
| Document | Pages | PDF size | Compile time | Peak memory |
|---|---|---|---|---|
| Invoice | 2 | 14 KB | about 4 ms | 46 MB |
| Account report | 30 | 539 KB | about 305 ms | 109 MB |
| Long report | 300 | 5.4 MB | about 3.6 s | 690 MB |
A few things stand out:
- The two-page invoice compiles in single-digit milliseconds. A full API call takes longer, about three-quarters of a second from India, and almost all of that is network and database time rather than rendering (where the latency goes). The compile itself is the part a headless browser cannot match, even when warm.
- Time grows linearly with pages, at roughly a millisecond per page for this text-and-table template. There is no cliff where the document stops fitting in a browser's memory.
- Peak memory for the 300-page report stayed under 700 MB, so it fits comfortably in the 2 GB service.
- A new server instance adds about 130 ms the first time it runs, once per instance, not per render.
Heavier templates take longer: our GST invoice starter, with a UPI QR code, compiles in about 20 ms on the production service, where each render also runs in its own process for isolation. The shape holds: compile time is a function of the document, not of a browser's start-up.
Chrome and Typst on the same document
We didn't start with Typst. Until the end of October, PageRush turned web pages into PDFs with headless Chrome, using Cloudflare's hosted browser service, and those renders took between 1.2 and 8.7 seconds through our API. They were different documents from the ones above, though, so on 10 November we ran a direct comparison.
We wrote one account statement twice: as a Typst template, and as an HTML page with the same layout, a repeating table header and a "Page 3 of 12" footer. The table holds 60, 1,200 or 12,300 transactions, which is 2, 30 or 300 pages. Chrome ran on Cloudflare's hosted browser service, which reports the browser time each PDF used. Typst ran on our compile service in Mumbai, which reports its compile time, including starting a separate process for each render. Each render had different data, and the times below are the median of 20 runs, with the 95th percentile in brackets. The 300-page Typst figure is from 5 runs and uses the compile service's total running time, which slightly overstates it.
| Document | Chrome browser time | Typst compile time |
|---|---|---|
| 2 pages | 217 ms (672 ms) | 24 ms (34 ms) |
| 30 pages | 998 ms (2.3 s) | 407 ms (429 ms) |
| 300 pages | failed three times | 4.4 s (4.5 s) |
What the numbers show:
- Short documents are where the difference is largest: about nine times faster at the median, and twenty times at the 95th percentile.
- At 30 pages Typst was about two and a half times faster at the median. Chrome was the less predictable of the two: its slowest run took more than twice its median, while Typst's runs stayed within 40 ms of each other.
- At 300 pages Chrome did not produce a PDF. It timed out after 95 seconds, failed with "Printing failed" after 114 seconds at the service's longest allowed timeout, and returned an internal error after 184 seconds without the page footer.
This statement is a heavier template than the invoice in the first table, which is why its Typst times are higher. The same HTML came out as 33 pages in Chrome rather than 30, because the fonts and spacing differ slightly.
We also timed whole API calls from India, the way a customer sees them, over a connection that was already open. Cloudflare's browser API took a median of 1.5 seconds for 2 pages and 2.7 seconds for 30. The PageRush API took 0.76 and 1.6 seconds, including authentication, the quota check and storing the PDF. Most of the difference between these and the table above is network time, which the latency post looks at. We couldn't time the 300-page document this way, because Chrome produced nothing to time.
What you give up
Typst is not HTML, and that is a real cost. Your team has to learn a new markup, and existing HTML templates have to be rewritten. We think the trade is worth it for documents that are generated repeatedly from data, which is what an API is for. Invoices, statements, certificates, payslips and reports are written once and rendered thousands of times. An hour spent learning Typst's table syntax pays for itself the first time a header repeats correctly on page two.
If you would rather not write Typst at all, you don't have to. PageRush's visual template editor lets you place text, tables, images, charts and barcodes on the page and bind them to your data fields, and the result renders through the same API as a hand-written template, on every plan.
We are also testing our own AI assistant inside the visual editor. You describe the document you want in plain words, such as "a GST invoice with our logo, the line items and a tax summary", and it lays the template out for you, binding the parts to your data fields. It works through the same editing tools you would use, so every change appears on the canvas and can be undone, and you never see or write any Typst. It isn't open to customers yet; we will say so here when it is.
If you do write Typst, you don't have to learn it alone. General-purpose AI chatbots such as ChatGPT, Claude and Gemini write Typst, and they are good at turning an existing HTML template, or a description of the invoice you want, into a first draft. Typst changes faster than HTML, so a chatbot sometimes reaches for syntax from an older release. Paste the error back in and it can usually correct itself. PageRush keeps that loop short: a template that doesn't compile comes back as a 422 with Typst's own messages and line numbers, and llms.txt gives a chatbot our conventions, such as reading the data with json("data.json"), in a single file it can read.
There is also a lot that Typst makes easier than HTML ever did. Reading the render data is one line:
#let data = json("data.json")
After that, loops, conditions and arithmetic are ordinary code, and #data.customer.name is just a value. QR codes, barcodes and charts come from Typst packages that are bundled with the service, so nothing is fetched at render time.
Where this leaves us
Choosing a compiler over a browser is why PageRush can promise fast renders for long documents, reproducible output for versioned templates, and pricing that does not have to cover a fleet of idle browsers. The follow-up posts cover what a render actually costs us and where the remaining latency goes, because once compiling takes 5 ms, everything else becomes the interesting part.