Comparisons

WeasyPrint vs Playwright for HTML to PDF: real timings and when each wins

We rendered the same invoices, reports and web pages with WeasyPrint and headless Chromium via Playwright. Cold and warm timings, memory, and the cases where one is simply the wrong tool.

M Mikel Rougstone · 31 August 2026 · 4 min read
WeasyPrint vs Playwright for HTML to PDF: real timings and when each wins

Both tools turn HTML into PDF. They are not interchangeable, and the difference is not quality — it is what kind of HTML you feed them. We measured both on the same machine with the same inputs so the choice can be made on numbers instead of vibes.

The setup

One server, 8 cores, 32 GB RAM, Ubuntu 24.04. WeasyPrint 69 and Playwright 1.62 driving Chromium. Three inputs, each rendered 50 times, medians reported:

  • Invoice — one page, tables, a logo, system fonts
  • Report — 14 pages, two SVG charts, one web font, repeated table headers
  • Marketing page — a live URL whose main sections are rendered client-side

Timings

InputWeasyPrintChromium (warm)Chromium (cold)
Invoice, 1 page0.19 s0.64 s1.94 s
Report, 14 pages1.9 s1.1 s2.4 s
JavaScript pagelayout wrong2.4 s3.7 s

The invoice result surprises people. WeasyPrint is three times faster on simple documents because there is no browser in the path at all — it is a layout engine writing PDF directly.

The report flips the order. WeasyPrint's layout pass grows with page count and complex floats, while Chromium's engine was built for exactly that workload and barely notices the extra pages.

The third row is not a contest. WeasyPrint executes no JavaScript, so it renders the empty shell.

Cold-start cost only appears once per process. If you keep a browser alive between renders — which
any serious setup does — you pay 1.9 s once, then 0.64 s per document.

Memory

WeasyPrintChromium
Idle~0 (library)300–400 MB (browser process)
Peak, 14-page report~120 MB~150 MB above idle
After 1,000 rendersflatgrows; recycle every few hundred

If you render one document a minute, WeasyPrint is dramatically cheaper — there is nothing running between requests. If you render one a second, the browser is already warm and only the per-document cost matters.

Where WeasyPrint wins

  • Typography on long documents. It implements paged-media CSS properly: running headers,

footnotes, page counters, string-set, break-inside on tables. Contracts and statements come out looking typeset rather than screenshotted.

  • Cost per document. No browser process, no pool, no recycling.
  • Deployment. pip install weasyprint and a few system libraries versus a 400 MB browser.
  • Determinism. No timing questions — nothing is loading asynchronously.

Where Chromium wins

  • Anything with JavaScript. Charts drawn client-side, frameworks that mount after load, lazy

images, dashboards behind a login.

  • Fidelity to the browser. If the requirement is literally "the PDF must look like the page",

only the browser can guarantee it.

  • Modern layout at scale. Grid, flexbox, container queries, aspect-ratio — all current.
  • Headers and footers with page numbers, supplied as separate templates rather than CSS tricks.

The decision, compressed

  • Authored as a document, no JavaScript, high volume → WeasyPrint
  • Comes from a web page, or must match the browser → Chromium
  • Not sure → Chromium. It is never wrong, only sometimes slower.

Running both

Nothing forces a single choice. PDFGeny keeps both behind one endpoint and lets the caller decide per request:

# document-shaped HTML: cheaper and crisper through WeasyPrint
curl -X POST https://pdfgeny.com/api/v1/render \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"html":"<table>…</table>","engine":"weasy"}' --output statement.pdf

# a real page: Chromium, waiting for the charts to finish
curl -X POST https://pdfgeny.com/api/v1/render \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://app.example.com/report","wait_for":".chart-ready"}' --output report.pdf

The template catalog routes table-heavy documents through WeasyPrint automatically, because that is where it is both faster and better.

Things that skew benchmarks

If you run your own comparison, three details change the numbers more than the engines do:

1. Cold versus warm. Including browser startup in every measurement makes Chromium look four times slower than it is in production. 2. Network. A web font or an image fetched over the internet adds latency that has nothing to do with either engine. Inline or cache assets before timing. 3. Page count. Single-page tests flatter WeasyPrint; long documents flatter Chromium. Measure the document you actually ship.

What we run in production

Chromium for anything arriving as a URL or containing scripts; WeasyPrint for the table-heavy templates in the catalog. Median across all traffic sits near 0.6 s, and the split is invisible to callers — they send a document and get a PDF.

M

Mikel Rougstone

Founder, PDFGeny

I build and run PDFGeny — the API, the rendering fleet and the template catalog. Most of what I write here comes from something that broke in production first.