How to Generate PDFs in Node.js Without Puppeteer
Puppeteer works, but it's 300 MB of Chrome you have to manage yourself. Here are the real alternatives — and when to use each one.
Generating PDFs in Node.js sounds straightforward until you actually try it. The most common advice is "just use Puppeteer" — but Puppeteer ships a full Chromium binary (~300 MB), requires specific Linux dependencies, behaves differently across environments, and is notoriously painful to run in serverless or containerized deployments.
This guide covers every real option, their tradeoffs, and when to use each one.
Option 1: Puppeteer (the standard, but expensive)
Puppeteer controls a headless Chrome instance and can print any page to PDF. It produces excellent output — real CSS, real fonts, accurate layout. The problems:
- Binary size: The bundled Chromium is ~300 MB. On Lambda, Vercel, or Fly.io this is a significant cold-start and deployment concern.
- Linux dependencies: Chromium on headless Linux requires dozens of system libraries (
libgbm,libnss3, etc.) that aren't present in minimal container images by default. - Serverless unfriendly: Each cold start launches a new browser process. Even with connection pooling, concurrent renders in a serverless function often hit memory limits.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent('<h1>Hello</h1>');
const pdf = await page.pdf({ format: 'A4' });
await browser.close();This works. It's just expensive to host and maintain.
Option 2: wkhtmltopdf (avoid for new projects)
wkhtmltopdf is a command-line tool that converts HTML to PDF using a patched Qt WebKit. It predates modern CSS by a decade, has no active maintenance, and its CSS support is frozen at roughly 2013 levels — no flexbox, no grid, no CSS variables. Avoid it for anything that needs a modern layout.
Option 3: PDFKit / jsPDF (for simple documents only)
Libraries like PDFKit (Node.js) and jsPDF (browser/Node) generate PDFs programmatically — you describe the document in code rather than rendering HTML. They're fast and dependency-free, but the trade-off is real: you lose all the CSS layout power you get from HTML, and complex designs quickly become painful to maintain in imperative draw calls.
Good for: receipts with fixed layouts, simple text reports, forms. Bad for: anything that resembles a designed document.
Option 4: A render API (the serverless-friendly path)
If you don't want to run a headless browser yourself, a render API like Renderfy handles the Chromium infrastructure on its end. You POST your HTML or Tailwind template and receive a PDF binary in the response — no browser process to manage, no system dependencies to worry about, and it works identically in Lambda, Vercel Edge Functions, Fly.io, or a bare VPS.
const response = await fetch('https://renderfy.io/api/v1/render', {
method: 'POST',
headers: {
'Authorization': 'Bearer rfy_live_YOUR_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'tailwind',
content: `
<div class="p-10 font-sans">
<h1 class="text-3xl font-bold text-zinc-900">Invoice #1042</h1>
<p class="mt-2 text-zinc-500">Issued 14 July 2026</p>
</div>
`,
options: { format: 'pdf', dimensions: 'a4' },
}),
});
const pdfBuffer = Buffer.from(await response.arrayBuffer());
// stream to user, upload to S3, attach to email...The output is generated by a real Chromium instance server-side, so full CSS support, custom fonts, and complex layouts all work — you just don't have to run the browser yourself.
Which should you use?
- Long-running server, full control needed: Puppeteer with a warm browser singleton and connection pooling.
- Serverless or edge deployment: A render API — Chromium doesn't fit the serverless model cleanly.
- Simple documents with no CSS dependencies: PDFKit if the document structure is fixed and simple.
- Legacy codebase already using wkhtmltopdf: Migrate — its CSS limitations will eventually cause you problems.
A note on Markdown to PDF
If your source is Markdown rather than HTML — changelogs, release notes, documentation — a render API that accepts Markdown directly avoids the extra conversion step. Renderfy's type: "markdown" mode converts Markdown to a styled HTML document and renders it to PDF in one call.
The right answer depends on your deployment model. If you're already running a persistent Node.js server with memory headroom, Puppeteer with a reused browser process is a solid choice. If you're on serverless or want to avoid the ops overhead entirely, a render API is the pragmatic path.
Further reading
- How to Automate Invoice PDF Generation with an API — end-to-end guide to wiring PDF generation into your billing pipeline
- HTML to PDF API — full reference for rendering Tailwind and HTML to PDF
- Markdown to PDF API — convert changelogs, release notes, and documentation to PDF in one call