Why serverless Chromium is so painful on Vercel and Lambda
Serverless Chromium is painful because a function sandbox is the opposite of what a browser wants: a 50 MB code limit versus a ~150 MB browser, no system libraries, a cold start on every scale-up, and a hard timeout in the middle of a render. The reliable fix is to move rendering off the request path — either a warm dedicated worker or a hosted PDF API.
The four failure modes
Almost every "generate a PDF in a serverless function" project hits the same wall. It works locally, then breaks in production for one of four reasons.
- **Bundle size.** A full Chromium is ~150 MB. Vercel/Lambda cap the deployed function.
@sparticuz/chromiumships a stripped, compressed build to fit — but you have to wire it correctly and it is easy to bloat past the limit again. - **Cold starts.** The browser binary has to be decompressed and launched on the first request after a scale-up. That is multiple seconds before a single byte renders.
- **Missing system libraries.** Chromium links against fonts and shared libraries (
libnss3,libatk, emoji/CJK fonts) that aren't in the minimal serverless image. Miss one and you get a blank page or a launch crash. - **Timeouts.** A cold start plus a heavy render can blow past the function's max duration, so the render dies half-finished with an opaque 502.
Why local works but prod doesn't
Locally you have the full system Chromium, all fonts, no bundle limit, and no cold start. The serverless runtime strips all four away at once, so the same code that renders a clean invoice on your laptop returns a blank or broken PDF in production.
The pragmatic fixes
- **Warm, dedicated worker.** Keep one long-lived process with a launched browser and send it render jobs. No cold start, no bundle limit. This is what most teams graduate to.
- **Hosted PDF API.** Offload the whole problem: one HTTP call in, a PDF out. No Chromium in your bundle at all. See generate a PDF from any stack.
Docweave runs Chromium on a warm worker behind an API, so your function just makes one request. If you want to keep rendering yourself, at least move it off the request path onto a warm process — the four failure modes above all trace back to launching a browser inside a cold, stripped sandbox.
A one-call alternative
If you'd rather not manage any of this, send HTML and get a PDF back:
curl https://docweave.dev/api/v1/pdf \
-H "Authorization: Bearer dw_live_your_key" \
-H "Content-Type: application/json" \
-H "Accept: application/pdf" \
-d '{"source":{"type":"html","html":"<h1>Invoice #1024</h1>"}}' \
--output invoice.pdfFAQ
Can I run Puppeteer in a Vercel function?
You can, using @sparticuz/chromium to fit the bundle limit, but you still pay cold-start latency and risk missing-font/timeout failures. For reliability, move rendering to a warm worker or a hosted PDF API.
Why is my serverless PDF blank?
Usually a missing font or system library, or the function timing out mid-render. The minimal serverless image lacks fonts (including emoji/CJK) that Chromium needs, so text can render empty.
What's the simplest way to avoid serverless Chromium entirely?
Call a hosted PDF API: POST your HTML or a template + JSON and get a PDF back, with no browser in your deployment. That removes bundle-size, cold-start, font, and timeout problems in one move.
Related
Generate your first PDF in minutes.
Get an API key