Skip to content

HTML to PDF without running headless Chrome yourself

Running headless Chrome in your own app is a real operational burden: memory leaks, cold starts, and hosts that will not let you launch a browser at all. There are exactly two honest ways around it, and they trade off in opposite directions. Here is how to choose.

Headless Chrome renders HTML to PDF perfectly, and running it yourself is a genuine burden. It leaks memory under load, it adds hundreds of milliseconds of cold start when it launches, its major version bumps break your layout on nobody's laptop but production's, and plenty of hosts, from small serverless functions to locked-down managed platforms, will not let you launch a browser process at all. So the question comes up constantly: can I convert HTML to PDF without running headless Chrome? Yes, and there are exactly two honest ways to do it. They pull in opposite directions, and picking the wrong one is how teams end up unhappy.

The short version

You have two real options for avoiding a browser in your own app. First, use a pure-code rendering engine like WeasyPrint that draws the PDF from HTML and CSS without any browser, which is lightweight and self-contained but supports only a subset of modern CSS and runs no JavaScript. Second, keep browser-grade rendering but move the browser off your servers by calling a hosted API, which handles modern CSS and JavaScript perfectly but sends your HTML to a third party. The choice is a straight trade: give up rendering fidelity to keep everything local, or keep the fidelity and give up running it yourself. There is no option that is both pure-code and pixel-identical to Chrome, because matching a browser is precisely what requires a browser.

Option 1: a pure-code engine, no browser at all

The first path replaces the browser with a library that implements its own HTML and CSS layout in code. The best known is WeasyPrint (Python), and there are peers in most ecosystems: dompdf and mPDF in PHP, OpenHTMLtoPDF in Java, Prince and its API-based cousins on the commercial side. These engines read your HTML and stylesheet and paint a PDF directly, with no Chromium, no Node, and no separate process. They are small, they start instantly with no cold-start penalty, and they are easy to bundle into a serverless function or a slim container.

The catch is CSS coverage, and it is a real one. A pure-code engine only supports the CSS its authors have implemented, which is always a subset of what a browser does. WeasyPrint has strong support for print CSS, page breaks and paged media, better than most, but flexbox and grid support is partial and modern layout tricks can surprise you. dompdf is stuck around CSS 2.1, so a Tailwind template collapses in it entirely. And none of these engines run JavaScript, so a chart drawn client-side or a value populated by a script simply will not appear. The practical rule: if you control the markup and can write clean, print-oriented, mostly static HTML and CSS for the document, a pure-code engine is a fast, cheap, fully local answer. If the document is a real app page with a modern design system or any client-side rendering, it will let you down, and no amount of tweaking closes the gap.

Option 2: keep the browser, just not on your servers

The second path accepts that matching a browser requires a browser, and simply moves that browser somewhere else. A hosted rendering API runs managed Chromium on the vendor's infrastructure, so from your application it is one HTTP request. You never launch Chrome, never patch it, never tune a memory pool, and never hit the wall where your host forbids spawning a browser process, because the browser is not in your process at all.

curl https://api.sitepdf.com/v1/render \
  -H "Authorization: Bearer $SITEPDF_KEY" \
  -F url=https://app.example.com/invoices/8842 \
  -F format=Letter \
  -F archive=true

Because a real, current browser does the work, everything renders the way it does on screen: flexbox, grid, web fonts, gradients, and JavaScript-driven content all behave. This is the option that fits a serverless function or a managed platform cleanly, since the compute-heavy, memory-hungry part lives with the vendor and your function stays tiny. The trade is that your HTML, or a URL to it, leaves your network and is rendered by a third party, which is a data-residency question you should answer honestly before you commit. If your content cannot leave your environment for regulatory reasons, this path is out and a pure-code engine or a self-hosted service is your only route.

There is also a capability a hosted API can add that a local library cannot: a record. With archive=true, the render is also stored as a timestamped, immutable snapshot, retrievable by id, so you keep a dated copy of exactly what you produced. The PDF generator API page covers the full call, and the website archiving page explains how the record works.

What about a self-hosted browser service?

There is a middle position worth naming. If you want browser fidelity, cannot send data to a third party, but also do not want Chrome inside your application process, run a browser as a separate service you control, most commonly Gotenberg in its own Docker container. Technically you are still running headless Chrome, but it is isolated: a render that crashes takes down a Gotenberg pod, not your API, and you scale and memory-limit it independently. It keeps everything inside your network at the cost of operating that infrastructure yourself. The Gotenberg alternative page runs that self-host versus hosted decision honestly, and the broader library-versus-service maths is in HTML to PDF library vs API.

How to choose

Your situationBest fit
Simple, static, print-style HTML you fully controlPure-code engine (WeasyPrint, dompdf)
Real app pages, modern CSS or JavaScript, output must match ChromeHosted rendering API
Browser fidelity but data cannot leave your networkSelf-hosted browser service (Gotenberg)
Serverless or a host that forbids launching a browserHosted rendering API
You need a dated, verifiable record of each PDFHosted rendering API with archiving

The honest bottom line is that "without headless Chrome" splits into two very different wishes. If you mean "without a heavyweight rendering engine at all," a pure-code library gives you that, and you accept the CSS limits as the price. If you mean "without me having to run and babysit a browser," a hosted API gives you full browser fidelity while someone else operates it, and you accept sending the HTML out. Name which of the two you actually want, and the right tool is obvious. Whichever you pick, background jobs that generate documents on a schedule are worth watching with uptime monitoring so a silently failing render pipeline does not go unnoticed for a week.

Written by the team building Sitepdf, an HTML to PDF API that archives every page it renders. The in-browser converter is free to try; early access locks the launch pricing.

§ Early access

Get on the early-access list

The API opens to the list first, in order. Early access locks the planned launch rates for 12 months. No card required, launching soon.

Render + archive, one API