How to Generate PDFs from HTML in Node.js
Converting HTML to PDF is one of the most common requirements in web applications. Whether you need invoices, reports, or printable documents, there are several ways to generate PDFs from HTML in Node.js. This guide covers three approaches with working code examples.
#Why generate PDFs from HTML?
HTML and CSS give you precise control over document layout. Instead of learning a PDF-specific library, you can use the same skills you already have. Common use cases include:
- Invoices and receipts with dynamic data
- Reports and dashboards exported for stakeholders
- Contracts and agreements that need signatures
- Tickets and boarding passes for events or travel
Leverage your existing skills
If you already know HTML and CSS, you don't need to learn a new templating language. Design your PDF in the browser, then convert it.
The challenge is rendering HTML exactly as it appears in a browser, including web fonts, CSS layouts, and images.
#Option 1: Puppeteer
Puppeteer is a Node.js library that controls a headless Chrome browser. It renders your HTML with full browser fidelity and can export to PDF.
#Installation
npm install puppeteer
#Basic example
const puppeteer = require('puppeteer');
async function htmlToPdf(html) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
});
await browser.close();
return pdf;
}
// Usage
const html = `
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>Invoice #1234</h1>
<p>Thank you for your purchase.</p>
</body>
</html>
`;
htmlToPdf(html).then(pdf => {
require('fs').writeFileSync('invoice.pdf', pdf);
console.log('PDF created');
});
#Pros and cons
Pros:
- Perfect browser rendering fidelity
- Supports modern CSS (flexbox, grid, custom fonts)
- Active development and large community
Cons:
- Downloads a 300MB+ Chromium binary
- High memory usage (150-300MB per instance)
- Cold starts take 2-5 seconds
- Difficult to run in serverless environments
Memory considerations
Each Puppeteer instance can consume 150-300MB of RAM. If you're generating PDFs concurrently, memory usage can spike quickly. Consider using a browser pool or queue system for production workloads.
#Option 2: wkhtmltopdf
wkhtmltopdf uses the WebKit engine to render HTML. It is a command-line tool that you can call from Node.js.
#Installation
# macOS
brew install wkhtmltopdf
# Ubuntu/Debian
sudo apt-get install wkhtmltopdf
# Node.js wrapper
npm install wkhtmltopdf
#Basic example
const wkhtmltopdf = require('wkhtmltopdf');
const fs = require('fs');
const html = `
<html>
<body>
<h1>Invoice #1234</h1>
<p>Thank you for your purchase.</p>
</body>
</html>
`;
wkhtmltopdf(html, { pageSize: 'A4' })
.pipe(fs.createWriteStream('invoice.pdf'));
#Pros and cons
Pros:
- Faster than Puppeteer for simple documents
- Lower memory footprint
- Works well in Docker containers
Cons:
- Uses an older WebKit engine (limited CSS support)
- No flexbox or CSS grid support
- Web fonts can be problematic
- Project is no longer actively maintained
Legacy tool
wkhtmltopdf is no longer actively maintained. While it still works for basic use cases, consider alternatives if you need modern CSS features or long-term support.
#Option 3: PDF generation API
A PDF API handles the browser rendering on dedicated infrastructure. You send HTML and receive a PDF URL or binary.
#Why use an API?
Running headless browsers in production creates operational overhead:
- Memory management: Chrome can consume 300MB+ per instance
- Timeouts: Complex pages may take 10+ seconds to render
- Scaling: You need to manage browser pools for concurrent requests
- Dependencies: Binary dependencies complicate deployments
An API abstracts this complexity. You make an HTTP request and get a PDF back.
#Example with PDFLoom
const https = require('https');
async function htmlToPdf(html, options = {}) {
const response = await fetch('https://api.pdfloom.com/v1/convert/html', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: html,
options: {
format: options.format || 'A4',
printBackground: true,
...options
}
})
});
const data = await response.json();
return data.url; // Signed URL valid for 30 minutes
}
// Usage
const html = `
<html>
<head>
<style>
body { font-family: 'Helvetica', sans-serif; padding: 40px; }
.header { border-bottom: 2px solid #333; padding-bottom: 20px; }
.amount { font-size: 24px; font-weight: bold; }
</style>
</head>
<body>
<div class="header">
<h1>Invoice #1234</h1>
</div>
<p>Amount due: <span class="amount">$99.00</span></p>
</body>
</html>
`;
htmlToPdf(html).then(url => {
console.log('PDF available at:', url);
});
#Pros and cons
Pros:
- No binary dependencies
- Works in any environment (serverless, edge, containers)
- Scales automatically
- Consistent rendering across all requests
Cons:
- Requires network requests (adds latency)
- Ongoing cost per conversion
- Depends on third-party availability
Best for serverless
PDF APIs are the only option that works reliably in serverless environments like Vercel, Netlify, or AWS Lambda where you can't install system binaries or run long-lived browser processes.
#Comparison table
| Feature | Puppeteer | wkhtmltopdf | PDF API |
|---|---|---|---|
| CSS support | Excellent | Limited | Excellent |
| Memory usage | High | Medium | None |
| Setup complexity | Medium | Medium | Low |
| Serverless friendly | No | Partial | Yes |
| Maintenance burden | High | Medium | None |
| Cost model | Infrastructure | Infrastructure | Per request |
#Which approach should you use?
Choose Puppeteer if:
- You need perfect browser fidelity
- You have dedicated servers with plenty of memory
- You are already running Chrome for other tasks
Choose wkhtmltopdf if:
- Your HTML is simple (no flexbox, grid, or web fonts)
- You want a lightweight solution
- You are comfortable with a legacy tool
Choose a PDF API if:
- You want to avoid managing headless browsers
- You are deploying to serverless or edge environments
- You prefer predictable per-request pricing
- You need to scale without infrastructure changes
#Next steps
If you want to try the API approach, you can create a free PDFLoom account and get 50 credits to test your first conversions. The API documentation covers additional options like headers, footers, page ranges, and more.
For more advanced use cases, check out converting URLs to PDF or Office documents to PDF.
Related posts
PDF Generation in Serverless Environments
Learn how to generate PDFs in serverless environments like Vercel, AWS Lambda, and Netlify. Includes working code examples and a comparison of approaches.
Convert Any URL to PDF with a Single API Call
Learn how to convert any public URL to a PDF or screenshot using the PDFLoom API. Includes code examples in cURL, Node.js, and a full Express.js integration.
Puppeteer PDF: Common Problems and How to Fix Them
Troubleshoot Puppeteer PDF issues: timeouts, memory leaks, Docker problems, and font rendering. Working code examples and solutions.