Prompt
How do I convert HTML into a PDF reliably?
Latest observation
Converting HTML to PDF reliably depends a lot on what “HTML” means in your case:
- simple, static HTML/CSS
- modern web pages with JS rendering
- invoices/documents with precise print layout
- server-side generation at scale
Here are the most reliable approaches.
1) Use a browser engine for best fidelity
For most cases, the most reliable option is to render HTML in a real browser engine and print to PDF.
Good options
- Headless Chrome / Chromium
- Tools: Puppeteer (Node.js), Playwright
- Best for modern CSS, web fonts, flexbox/grid, SVG, and JS-rendered content
- WebKit-based rendering
- Sometimes useful if your target environment is Safari-like
Why this is reliable
Browsers are the real HTML/CSS renderers, so the PDF output matches what users see.
Example with Puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
});
const page = await browser.newPage();
await page.goto('file:///path/to/document.html', { waitUntil: 'networkidle0' });
await page.pdf({
path: 'output.pdf',
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
bottom: '20mm',
left: '15mm',
right: '15mm'
}
});
await browser.close();
2) Use print CSS
If you want the PDF to look good, add a dedicated print stylesheet.
Key things to include
@media print- page size and margins with
@page - hide navigation/buttons
- avoid splitting important blocks across pages
Example:
@media print {
body {
margin: 0;
font-size: 12pt;
}
.no-print {
display: none !important;
}
@page {
size: A4;
margin: 20mm;
}
h1, h2, h3 {
page-break-after: avoid;
}
.section {
break-inside: avoid;
}
}
3) Wait for all assets before generating PDF
A common source of unreliability is timing.
Make sure:
- fonts are loaded
- images are loaded
- JS-rendered content is complete
- any API calls are finished
For Puppeteer/Playwright, use something like:
waitUntil: 'networkidle0'- explicit waits for selectors
- font loading checks
Example:
await page.goto(url, { waitUntil: 'networkidle0' });
await page.evaluate(() => document.fonts.ready);
4) Prefer absolute or fully resolvable asset URLs
Relative paths often fail in PDF generation.
Use:
- absolute URLs
- embedded data URIs for small images/icons
- local file access only if the tool is configured for it
5) Avoid layout features that don’t print well
Even with a browser engine, some things can be tricky:
- huge tables
- fixed-position headers/footers
- complex flex/grid layouts spanning pages
- absolutely positioned elements
- content that depends on viewport size
- page breaks inside transformed elements
For long documents, table pagination and page headers/footers need special handling.
6) For high-volume or enterprise use, consider dedicated rendering tools
If you need a server-side API or batch conversion, good options include:
- PrinceXML — very high quality for print/PDF
- Antenna House
- DocRaptor — hosted API using Prince
- WeasyPrint — good for simpler HTML/CSS, especially Python workflows
These can be more predictable for paginated documents than a browser, especially with advanced page styling.
7) Common pitfalls
- Missing fonts
- CORS issues on images/fonts
- CSS loaded too late
- Page size defaults not set
- Browser sandbox issues in containers
- Different results between local and production environments
Practical recommendation
- Need modern HTML/CSS/JS fidelity? Use Playwright or Puppeteer
- Need print-perfect documents and can pay for it? Use PrinceXML/DocRaptor
- Need Python and simpler documents? Try WeasyPrint
If you want, I can give you:
- a Node.js example using Puppeteer or Playwright,
- a Python example, or
- a recommended setup for invoices/reports.