It will not run
Chromium does not fit in an AWS Lambda deployment package
The deploy fails on the unzipped size limit, or it deploys and the first invocation times out on cold start.
What is actually happening
A full browser build is several hundred megabytes and the unzipped package limit is 250MB. This is why a genre of size-reduced browser packages exists. Even when it fits, the cold start has to load that binary before your code runs, which is seconds of latency on the first request after every scale-up.
Confirming it is this and not something that looks like it
Compare your unzipped package size against the limit. This one is arithmetic rather than diagnosis.
The fix
Move the render out of the function. A Lambda that makes an HTTP call has a package measured in kilobytes, a cold start measured in milliseconds, and no binary to keep compatible with the runtime as it is upgraded.
// The whole function. No layer, no binary, no cold start penalty.
export const handler = async (event) => {
const html = await buildDocument(JSON.parse(event.body));
const upstream = await fetch("https://api.pdfpipe.xyz/v1/pdf", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PDFPIPE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ html, options: { format: "A4", printBackground: true } }),
});
return {
statusCode: 200,
isBase64Encoded: true,
headers: { "Content-Type": "application/pdf" },
body: Buffer.from(await upstream.arrayBuffer()).toString("base64"),
};
};If that was not it
These produce the same symptom often enough to be worth ruling out before assuming the fix above did not work.
- A layer that fits but pushes the total past the limit when combined with your dependencies
- The /tmp limit, which a browser will use for its own scratch space
- Function memory sized for JSON, where a browser needs an order of magnitude more
- The same limits, at different numbers, on other serverless platforms
Frequently asked
Does this happen with every rendering engine?
The behaviour behind it is not specific to one tool. A full browser build is several hundred megabytes and the unzipped package limit is 250MB. Anything rendering HTML to a paged medium has to make the same decision, so the fix travels with you if you change how the render happens.
Will this show up as an error in my logs?
Yes, this one surfaces as a thrown error or a failed status, which is why it is at least findable. The harder half is that the message often names the call that was in flight rather than the thing that actually failed.
Related failures
Problems people arrive at from the same starting point, or mistake for this one.
Failed to launch the browser process
A headless browser is not a self-contained binary.
libnss3.so: cannot open shared object file
This is the previous error with the first missing library named.
Navigation timeout of 30000 ms exceeded
The default wait condition is the network going idle, and something on the page is keeping it busy: an analytics beacon, a font request to a host that is not responding, a polling request, or an ad script.
Protocol error (Page.printToPDF): Target closed
The tab crashed, and by far the most common reason is memory.
PDF generation runs out of memory
Two multiplying factors.
CSS for paged documents, and what actually works
Which parts of the paged media specification a browser-based render implements.
Everything that goes wrong, by category
The full list, grouped by where in the pipeline it breaks.
Paste your markup and see the rendered document, without signing up.