pdfkitt vs Puppeteer: HTML to PDF Without the Headache
Puppeteer is a great library — until it becomes your full-time job. Here is how self-hosting stacks up against an API built for production PDFs.
The problem with self-hosted Puppeteer
Running headless Chromium on your own servers looks simple in a tutorial. In production it is a different story. Each browser instance consumes significant RAM — often hundreds of megabytes per tab — and spikes under concurrent renders. CPU contention slows the rest of your app when invoices, reports, and exports all hit at once.
Crashes and stuck processes are routine: zombie Chromium, out-of-memory kills, and CI breaking when Chrome revisions change. Then there is ongoing maintenance: patching browsers, tuning launch flags, managing fonts and locales, hardening sandboxes, and building retries and timeouts so one bad HTML payload does not take down a worker.
Side-by-side: setup, cost, maintenance, scaling
| Self-hosted Puppeteer | pdfkitt API | |
|---|---|---|
| Setup time | Hours to days — Chromium images, fonts, flags, sandboxing, CI | Minutes — sign up, copy an API key, send a POST request |
| Cost | Always-on RAM/CPU, storage, plus ongoing engineering time | Predictable monthly plans; generous free tier (1,000 PDFs/mo) |
| Maintenance | Chrome updates, crash recovery, memory leaks, security patches | Fully managed infrastructure and rendering pool |
| Scaling | You provision workers, queues, retries, and rate limits | Scales with your plan; rate limits and quotas documented upfront |
Code: fifteen lines of plumbing vs three lines to ship
A minimal Puppeteer flow ignores pooling, health checks, and failure modes — but it is already more code than calling an API.
Puppeteer (typical setup)
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"],
});
try {
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle0", timeout: 30_000 });
const pdf = await page.pdf({ format: "A4", printBackground: true });
// Production: add pooling, logging, retries, and metrics here
await fs.promises.writeFile("out.pdf", pdf);
} finally {
await browser.close();
}pdfkitt (one request)
curl -X POST https://api.pdfkitt.dev/v1/convert \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"html":"<h1>Hello</h1>"}' --output out.pdfWhen Puppeteer still makes sense
Self-hosting is the right tool when you need maximum control: heavily customized Chromium builds, exotic DevTools protocol workflows, or features outside PDF generation — for example full interactive browser automation, screenshots in many formats, or air-gapped / on-prem environments where outbound API calls are not allowed.
If your goal is reliable HTML → PDF for invoices, reports, and user exports, an API trades flexibility you do not need for uptime you do.
Try pdfkitt free — 1,000 PDFs/month, no credit card
Get an API key and send your first PDF in minutes.