PDF generation in Node: Stop shipping headless browsers
Why I stopped shipping headless browsers for PDF generation and switched to a lightweight HTTP API approach ? less overhead, better scaling.
I used to spin up Puppeteer for every PDF generation task. Invoice, receipt, report—headless Chrome every time. Then I realized I was shipping a 500MB dependency to fill a form.
The problem: headless browsers are overkill 90% of the time. You're not rendering complex layouts or JavaScript. You're filling a template with data.
Two years ago I switched to pdf-lib for in-process PDF manipulation. No browser, no external calls. Works great for simple cases—stamping text, flattening forms, adding signatures.
But then I hit the scaling wall. Processing 10,000 invoices concurrently? pdf-lib blocks the event loop. Single PDF takes 200ms, multiply that across requests.
Now I use an HTTP API approach. I POST the template and data, get back the filled PDF. Looks like waste at first—extra network call, separate service. But here's the win:
Decoupling. Your Node app doesn't care how the PDF gets made. Could be pdf-lib, LibreOffice, a Ruby service, whatever. Change implementations without touching application code.
Scale independently. PDF service runs on different hardware. CPU-heavy? Spin up more instances without bloating your main app.
Reusability. Every microservice that needs PDFs uses the same endpoint. I've done this across ERPNext and Odoo integrations—centralized, battle-tested.
Node 18+ made this elegant. fetch and FormData are built-in. One POST call, zero npm dependencies:
const form = new FormData();
form.append('template', 'invoice');
form.append('data', JSON.stringify(orderData));
const response = await fetch('http://pdf-service/render', {
method: 'POST',
body: form
});
const pdf = await response.arrayBuffer();
Same code runs on Lambda, Workers, or bare metal. No Puppeteer bloat. Clean.
Choose pdf-lib if you're filling one PDF in a batch job. Choose HTTP services if you need throughput or want to keep concerns separated. I lean HTTP now—it's paid for itself in maintainability.