May 9, 2026

Puppeteer vs PDF API: Performance and Cost Compared

PuppeteerPDF APIcomparisonperformanceDevOps

Puppeteer works well at low volumes. At scale, the operational overhead — memory spikes, Chromium version management, browser pool complexity — starts to dominate engineering time. A managed PDF API trades that overhead for a per-request cost.

This article compares the two approaches across dimensions that matter for production systems: performance, memory, concurrency, cost, and operational complexity. If you have already decided to migrate and want step-by-step code, see the Puppeteer to PDF API migration guide.

What Each Approach Actually Involves

Self-Hosted Puppeteer

Puppeteer is a Node.js library that controls a headless Chromium instance. To use it for PDF generation in production you need to:

  • Install and pin a Chromium version (and update it when security patches ship)
  • Build and maintain a browser pool to avoid per-request cold starts
  • Handle memory limits, zombie processes, and graceful restarts
  • Adjust Docker images to include Chromium (adds ~300MB)
  • Manage CI/CD breakage when Chromium download URLs change

None of these tasks are related to generating PDFs. They are the cost of running a browser in production.

Managed PDF API

A managed PDF API (such as FUNBREW PDF) accepts an HTML payload over HTTP and returns PDF bytes. There is no Chromium on your servers. Scaling, engine upgrades, and concurrency are all handled by the service.

The decision comes down to whether the engineering time saved by offloading Chromium operations outweighs the per-request cost of the API.

Performance Comparison

Response Time

Scenario Puppeteer Cold Start Puppeteer Warm Pool FUNBREW PDF API
Simple HTML (~10 KB) 3,000–5,000 ms 500–800 ms 300–600 ms
HTML with images (~100 KB) 4,000–8,000 ms 800–1,500 ms 500–1,200 ms
Complex CSS and images 6,000–15,000 ms 1,500–3,000 ms 800–2,000 ms
AWS Lambda (serverless) 8,000–15,000 ms 300–600 ms

Cold start figures include Chromium boot time. A warm browser pool is competitive for simple documents, but falls behind on complex templates where page rendering takes longer than the API network round trip.

In serverless environments (Lambda, Vercel, Cloudflare Workers), Puppeteer cold starts are especially painful because Chromium must boot inside a sandbox on every invocation. The managed API adds no cold-start penalty on your side. For Lambda-specific deployment, see the serverless PDF API guide.

Memory Usage

Puppeteer (browser pool, 3 processes):
  Idle:               600 MB – 1.5 GB
  During PDF gen:     1 GB – 2 GB+
  Peak (10 concurrent): 3 GB+

Managed PDF API (HTTP client only):
  Idle:               < 10 MB
  During PDF gen:     < 50 MB (buffering HTTP response)
  Peak (100 concurrent): similar — no scaling needed on your server

On a t3.medium (4 GB RAM), a Puppeteer browser pool with three processes leaves roughly 1–2 GB for the rest of your application. Adding concurrency requires larger instances or a separate PDF service.

Docker Image Size

# Puppeteer image
FROM node:20
RUN apt-get install -y chromium   # +300 MB
RUN npm install puppeteer          # +200 MB
# Total: 1 GB+

# API client image
FROM node:20-alpine
# No Chromium required
# Total: ~150 MB

A smaller image means faster CI builds, faster container startup, and lower registry storage costs. For container deployment patterns, see the Docker and Kubernetes PDF API guide.

Concurrency

Puppeteer (2 GB server, browser pool of 3):
  Max safe concurrency:  3–5 jobs
  Beyond that:           queue or OOM

Managed PDF API:
  Max concurrency:       effectively unlimited (rate-limit dependent)
  Scaling:               handled by the API provider

If you need to generate 50 PDFs simultaneously, Puppeteer requires either a very large instance or a distributed system. The managed API handles this at the service level.

Cost Comparison

Self-Hosted Costs

Infrastructure

AWS t3.medium (2 vCPU / 4 GB RAM):
  On-demand:   ~$33/month
  Reserved:    ~$20/month

Minimum to run a 3-process Puppeteer pool with headroom for the rest of your app.
Burst traffic often requires a larger instance or auto-scaling.

Engineering and Maintenance

Initial implementation (browser pool, timeouts, retries): 8–16 hours
Chromium version bumps (3–4 per year at 2–4 hours each):  6–16 hours/year
Incident response (OOM, zombie processes, ~1/month):       2–8 hours/incident
Estimated annual maintenance:                              20–50 engineer-hours

At a $100/hour blended rate, 30 engineer-hours per year is $3,000 in maintenance cost alone — before counting the productivity drag of context-switching to debug Chromium crashes.

CI/CD

Chromium downloads (100–200 MB) slow every build. Docker images over 1 GB increase push/pull times and registry storage. These are small individually but compound across a large team.

Managed API Costs

FUNBREW PDF free tier:   30 PDFs/month at no cost
Paid plans:              usage-based; see /pricing
Engineering maintenance: near zero (only API client changes)

Where the Break-Even Falls

Low volume (< 100 PDFs/month):
  Self-hosted: server + engineer hours >> API cost
  → API wins clearly

Medium volume (100–10,000 PDFs/month):
  Self-hosted: $30–100 server + 20–50 hours maintenance
  API: usage-based cost
  → When maintenance hours are included, API is usually cheaper

High volume (10,000+ PDFs/month):
  Self-hosted: dedicated infrastructure, dedicated ops time
  API: scales on the provider's side
  → Depends on your volume and team size, but the managed provider's scale
     efficiency is hard to match

The cost most teams undercount is engineer opportunity cost. Every hour spent managing Chromium is an hour not spent on the product.

Operations Comparison

Dimension Puppeteer (self-hosted) Managed PDF API
Chromium upgrades Pin version; test every update Not your concern
Security patches Manual; CVEs ship in Chromium Handled by provider
Scaling concurrency Manual pool tuning or infra scaling Auto-scaled by API
Monitoring Browser health, memory, zombie processes HTTP error rates only
Serverless Lambda layers, size workarounds Works out of the box
Docker image 1 GB+ (Chromium included) Lightweight (no Chromium)
On-call incidents OOM crashes, process leaks HTTP 5xx from provider

For a deeper look at security considerations when self-hosting Chromium, see the PDF API security guide.

CSS and Rendering Compatibility

Because FUNBREW PDF uses a Chromium-based engine, the PDF output is visually identical to Puppeteer output for the vast majority of templates. The same CSS engine, same font rendering, same @page handling.

Two edge cases to check when evaluating:

  1. page.emulateMediaType('screen'): If you called this in Puppeteer, note that the API defaults to print media type. Audit your @media screen and @media print rules.
  2. page.evaluate() DOM manipulation: The API does not run application JavaScript. Move that logic into your template rendering step.

For detailed CSS tips during migration, see HTML to PDF CSS tips.

How to Decide

Use the decision tree below to choose the right approach for your situation.

Use Self-Hosted Puppeteer If

  • You are prototyping and the volume is under 30 PDFs/month (cost is essentially zero)
  • Your security policy prohibits sending HTML to external services
  • You need page.evaluate() to run complex JavaScript before generating the PDF (and cannot move that logic to server-side rendering)
  • You are already heavily invested in Puppeteer for browser testing, and PDF generation is a minor feature

Use a Managed PDF API If

  • PDF generation is a non-trivial part of your product
  • You are on a serverless infrastructure (Lambda, Vercel, Cloudflare Workers)
  • Your Puppeteer setup is already causing OOM crashes, Chromium upgrade breakages, or CI/CD slowdowns
  • You want to keep Docker images lean
  • You need to scale concurrency beyond what a single server can handle

The Gradual Migration Path

You do not have to migrate all at once. A common pattern is to introduce a wrapper function:

// Thin wrapper — start by calling Puppeteer
async function generatePdf(html, options = {}) {
  // Phase 1: Puppeteer
  return generatePdfWithPuppeteer(html, options);

  // Phase 2: flip to API when ready
  // return generatePdfWithApi(html, options);
}

Migrate one PDF type at a time (invoices first, then reports, then certificates), using a feature flag to control the cutover. This gives you a rollback path while the team builds confidence in the API output. See the full migration procedure in the Puppeteer to PDF API migration guide.

Quick-Start Code

If you decide to try the API, here is the smallest possible Node.js example:

async function generatePdf(html) {
  const response = await fetch('https://pdf.funbrew.cloud/api/v1/pdf/generate', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.FUNBREW_PDF_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      html,
      options: {
        format: 'A4',
        margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
        print_background: true,
      },
    }),
  });

  if (!response.ok) throw new Error(`API error: ${response.status}`);
  return Buffer.from(await response.arrayBuffer());
}

Test your HTML in the Playground before writing any code. The free plan gives you 30 PDFs/month at no cost. For Python, PHP, Ruby, and Go examples, see the API quickstart by language.

Summary

Puppeteer (self-hosted) Managed PDF API
Cold start 3–8 s (Lambda), 1–3 s (warm) None
Memory per request 200–500 MB < 10 MB
Docker image 1 GB+ ~150 MB
Concurrency ceiling Server RAM bound Rate-limit bound
Annual maintenance 20–50 engineer-hours Near zero
Break-even ~30 PDFs/month Starts free

Puppeteer is a reasonable starting point. A managed PDF API is usually the better choice once PDF generation becomes a real production workload — the operational savings outweigh the per-request cost at almost every realistic volume. Try the Playground to verify output quality against your templates before committing.

Related

Powered by FUNBREW PDF