The HTML-to-PDF reflex
When a developer needs to generate a PDF programmatically, the first instinct is almost always the same: “I already know HTML and CSS — I’ll just render my document as a web page and convert it to PDF.” This is a reasonable thought. HTML is universal, CSS is powerful, and there are dozens of tools that will take your markup and spit out a PDF file.
The problem is that HTML was designed for screens, not for print. PDF is a fixed-layout page format. Translating one to the other requires a rendering engine that understands both worlds, and none of them do it perfectly. The result is a category of tools that work well enough for simple cases but become increasingly painful as your documents get more complex, your volume grows, or your users start noticing that page 2 cuts the table in half.
This article is a honest look at the HTML-to-PDF landscape, the specific problems these tools create at scale, and the alternative approach — template-based PDF generation — that an increasing number of teams are switching to.
The HTML-to-PDF landscape
Puppeteer / Playwright
The most popular approach in 2026. Launch a headless Chrome (or Firefox) instance, load your HTML, call page.pdf(). You get Chrome-quality rendering with full CSS support. The tradeoff: you are running an entire browser to generate a document. Each instance consumes 100–300MB of RAM, cold starts take 2–4 seconds, and you need to manage browser instance pooling in production.
wkhtmltopdf
The veteran tool, built on a patched Qt WebKit engine from 2009. Simple to use (pass HTML in, get PDF out), but effectively unmaintained since 2020. No CSS Grid, no custom properties, no modern web fonts without workarounds. Still found in thousands of production systems because it works and nobody wants to touch it.
Prince / DocRaptor
Prince is a commercial HTML-to-PDF engine with the best CSS print support in the industry. It handles @page rules, named pages, footnotes, cross-references, and advanced pagination better than any browser. DocRaptor is Prince-as-a-service. The downsides: Prince licenses start at $3,800. DocRaptor’s API pricing is steep for high volume. Both are excellent tools, but the cost puts them out of reach for many teams.
WeasyPrint
A Python library that renders HTML/CSS to PDF without a browser. It supports a useful subset of CSS (including Flexbox and basic Grid) and produces clean output. Limitations include no JavaScript support, incomplete CSS paged media spec implementation, and performance that degrades on complex layouts. Good for Python-native stacks that need a lightweight option.
The 4 problems with HTML-to-PDF
These are not theoretical concerns. They are the issues that surface after you have shipped your HTML-to-PDF pipeline and your users are relying on it.
1. Performance: headless browsers are heavy
Puppeteer and Playwright need a full Chromium binary (~300MB) and spin up a browser process for each render session. Even with a warm browser pool, you are paying for the overhead of a tool designed to render interactive web applications just to produce a static document. In our benchmarks, Puppeteer averages 4,200ms cold and 820ms warm for a single-page invoice. wkhtmltopdf is faster at 2,800ms but still spawns a process per request.
At 100 PDFs per day, this does not matter. At 10,000, you need dedicated infrastructure, memory monitoring, and crash recovery. At 100,000, you are running a browser farm.
2. Pagination: CSS page breaks are unreliable
The CSS break-before, break-after, and break-inside properties exist to control pagination. In practice, they are inconsistently implemented across rendering engines. Chrome handles break-inside: avoid reasonably well for simple block elements but fails with nested flexbox or grid layouts. Tables that span multiple pages are a persistent source of bugs — headers may or may not repeat, rows may split mid-cell, and orphaned content regularly appears at the top of a new page.
Prince handles these cases well. Everything else requires manual workarounds: injecting spacer divs, pre-calculating content height with JavaScript, or splitting your data across multiple HTML files and concatenating the resulting PDFs.
3. Consistency: browser rendering varies
The PDF you get from Puppeteer depends on the version of Chromium bundled with it. Update Puppeteer, and your PDFs may change — different font metrics, different line heights, different page break decisions. wkhtmltopdf uses a frozen WebKit build so this is less of an issue, but you trade consistency for a permanently outdated engine. WeasyPrint has its own rendering model that does not match any browser.
For documents with legal or financial significance (invoices, contracts, compliance reports), inconsistent rendering is not just annoying — it can be a liability.
4. Maintenance: CSS for print is a different skill
Writing CSS for screen is a well-understood discipline with mature tooling. Writing CSS for print is a niche specialty. @page rules, running headers, margin boxes, named strings, target-counter() for cross-references — these are real CSS specs that almost no frontend developer has ever used. The result is that your “HTML template” becomes a fragile mix of screen CSS, print CSS, and rendering-engine-specific hacks that only one person on the team understands.
HTML-to-PDF tools are conversion tools — they translate a screen format to a print format. Template-based tools are native document tools — they generate PDF directly from a format designed for fixed-layout pages. The gap between these two approaches widens with every additional page, table, and pagination requirement.
The template-based alternative
Template-based PDF generation inverts the HTML-to-PDF approach. Instead of writing a web page and converting it, you design a document template with placeholders and send data to an API that fills the template and returns a PDF. The template is authored in a format designed for print — not HTML.
The workflow looks like this:
- Design once: Create a template using a visual editor or a typesetting language. Define where data goes using variables like
{{ client_name }}or{{ line_items }}. - Send data: Make an API call with a JSON payload containing the variable values. No HTML rendering, no CSS, no browser.
- Get a PDF: The API returns a PDF binary in 200–500ms. Pagination, font embedding, and layout are handled by the template engine natively.
This approach eliminates the conversion step entirely. The template engine understands pages, margins, columns, headers, footers, and page breaks as first-class concepts — because it was built for documents, not websites.
Template-based APIs compared
| Criteria | Typsetter | CraftMyPDF | PDFMonkey | Carbone |
|---|---|---|---|---|
| Template language | Typst + Tera | Drag-and-drop | HTML + Handlebars | DOCX/XLSX + tags |
| Render speed | ~340ms | ~1,200ms | ~2,000ms | ~1,500ms |
| Typography quality | Excellent (Typst) | Basic | Browser-dependent | LibreOffice-level |
| Batch generation | Native CSV batch | Yes | Yes | Manual loops |
| Self-hostable | Cloud only | Cloud only | Cloud only | Yes (on-premise) |
| Template design | Code + visual editor | Visual drag-drop | HTML code only | Office templates |
| Free tier | 100 PDFs/mo | 50 PDFs/mo | Trial only | Community edition |
| Learning curve | Typst (new language) | Low (drag-drop) | Low (HTML) | Low (Office) |
Typsetter
Built on Typst, a compiled typesetting language that generates PDFs natively without a browser or LibreOffice. Templates combine Typst markup with Tera (Jinja2-like) templating for variables and loops. The result is precise typographic control, fast renders (~340ms), and layouts that handle pagination correctly by design. The tradeoff is learning Typst, though most developers pick it up faster than CSS print stylesheets.
CraftMyPDF
A drag-and-drop template builder with a JSON-driven API. Good for teams that want a visual design experience without writing code. The templates are designed in a browser-based editor and rendered server-side. Limitations appear with complex layouts — you are constrained by what the visual editor supports.
PDFMonkey
Templates are written in HTML with Handlebars syntax. If you already know HTML, the learning curve is minimal. Under the hood, it still renders HTML to PDF, which means you inherit the pagination and consistency issues described above — though PDFMonkey handles some of these better than raw Puppeteer by controlling the rendering environment.
Carbone
Uses LibreOffice as its rendering engine. You design templates in Word or Excel, add placeholder tags, and Carbone fills them and converts to PDF. Excellent for teams already working with Office documents. The PDF output quality depends on LibreOffice’s PDF export, which is acceptable but not typographically refined. Can be self-hosted, which is a significant advantage for regulated industries.
When HTML-to-PDF is actually the right choice
This article argues for template-based generation in most cases, but HTML-to-PDF genuinely wins in three specific scenarios. If your use case fits one of these, use Puppeteer or Prince without hesitation.
Existing web content
You have a web page that users already view in a browser and you need a “Download as PDF” button that produces an identical copy. The page already exists as HTML/CSS. Converting it is the correct approach — rebuilding it in a template language would be duplication.
JavaScript visualizations
Your document includes D3 charts, Canvas-rendered graphs, or interactive content that requires a real browser to produce. No template engine can execute JavaScript. Puppeteer is the right tool here.
Web page archiving
You need to capture third-party web pages as PDFs for compliance, legal, or archival purposes. You do not control the source HTML. A headless browser is the only option that can faithfully capture arbitrary web content.
Ask yourself: “Am I generating a document or converting a web page?” If the answer is a document — invoices, contracts, reports, certificates, pay slips — you want a template engine. If the answer is a web page, you want a browser.
Migration guide: Puppeteer to Typsetter
If you are currently generating data-driven documents with Puppeteer and want to switch to a template-based approach, here is what the migration looks like in practice.
Before: Puppeteer + HTML template
After: Typsetter API
What changes in the migration
- Delete: Puppeteer dependency, Chromium binary, browser pool logic, HTML template files, print CSS stylesheets.
- Create: A Typsetter template (either in the web UI or via the API). This is a one-time cost per document type.
- Replace: Your render function becomes a single
fetch()call. No filesystem, no process spawning, no memory management. - Keep: Your data layer stays identical. The same JSON you were passing to Handlebars now goes to the Typsetter API.
For most teams, the migration takes 1–2 days per document type. The ongoing time savings compound: no more debugging print CSS, no more Chromium version conflicts, no more memory leak investigations at 3 AM.
Start with your highest-volume document. If you generate 10,000 invoices per month with Puppeteer, migrating that single template eliminates the most infrastructure overhead immediately. Migrate remaining templates one at a time.
Cost and performance summary
| Approach | Render speed | Infra cost | Maintenance | Pagination |
|---|---|---|---|---|
| Puppeteer | 4,200ms cold / 820ms warm | $70+/mo server | 4–8 hrs/mo | CSS page breaks |
| wkhtmltopdf | 2,800ms | $10–50/mo server | Deprecated | Poor |
| Prince / DocRaptor | ~500ms | $3,800+ license | Low | Excellent |
| WeasyPrint | 1,500–3,000ms | $10–50/mo server | Medium | Decent |
| Typsetter | ~340ms | $0–99/mo API | Zero | Native |
Conclusion
The “HTML to PDF API” search query will continue to dominate because it maps to how developers think about the problem. But for the majority of document generation use cases — invoices, contracts, reports, certificates, pay slips, order confirmations — HTML is the wrong starting point. You are fighting a format that was designed for scrolling screens, not fixed pages.
Template-based PDF generation is not a compromise. It is the purpose-built approach: faster renders, reliable pagination, consistent output, and significantly less infrastructure to manage. Tools like Typsetter, CraftMyPDF, and Carbone each solve this problem differently, and the right choice depends on your stack and priorities.
If your use case requires a real browser — JavaScript-rendered visualizations, existing web content conversion, or page archiving — use Puppeteer or Prince. They are excellent tools for their intended purpose. For everything else, stop converting web pages and start generating documents.
Try Typsetter for free
100 PDFs/month on the free plan. Pre-built templates for invoices, contracts, and more. API key in 30 seconds.