What you actually ship
Stock Node print drivers download their own Chrome. Vercel published a walkthrough titled Deploying puppeteer with Next.js on Vercel (https://vercel.com/kb/guide/deploying-puppeteer-with-nextjs-on-vercel, last updated 10 Nov 2025). It still tells you not to ship the full driver. It names the 250 MB function bundle limit and the pair that people copy: the core driver plus @sparticuz/chromium-min. That package replaced chrome-aws-lambda. The README is explicit: it is a stripped binary plus decompress code plus a serverless argument list. It is not a driver version. You pick a browser major that matches the published support table, then you keep those two packages in lockstep. At runtime the function fetches a remote pack from a URL you configure. Forum threads on Vercel show the same pattern: it works on the laptop, then production cannot find the files under the traced task directory. The next move is a remote pack on GitHub Releases or in public assets, plus serverExternalPackages so Next does not pack the module. That is the layer. It is also the failure surface.
Why the layer keeps breaking
The bundler. @sparticuz/chromium finds files with relative paths. The project README says that if you bundle with webpack, esbuild, or rollup you must mark the package external. Next.js now lists those packages and the common print drivers in the default serverExternalPackages set. That list is a compatibility bandage. Teams still add outputFileTracingIncludes, then watch a deploy omit the bin directory. The Next 15 rename from experimental.serverComponentsExternalPackages to serverExternalPackages is enough to silently drop a working config on upgrade.
The pin. The driver documents a preferred browser for each release. The community package is versioned to the browser, not to the driver. There is no semver story that makes the pin boring. A lockfile bump can desync the driver from the community browser build.
The first request. The min package still has to materialize a browser on a cold instance. Public write-ups of the same pattern (Marcel Fetten, Rendering PDFs on Vercel with Next.js) describe concurrent first hits sharing one temp tree. You pay the fetch more than once, or the tree is half-written and the next start fails. Holding one shared promise is a workaround, not a product. You also now depend on whoever hosts the pack remaining up, and on tmp being large enough this week.
The runtime. Edge cannot execute a native browser. The function must be Node. Hobby, Pro, and Enterprise do not share the same duration and memory ceilings. Vercel Functions limitations (https://vercel.com/docs/functions/limitations) still list 250 MB uncompressed as the standard size, including layers. Hobby memory is 2 GB / 1 vCPU. Pro and Enterprise can go to 4 GB / 2 vCPU. A real invoice DOM will use that. An unclosed process leaks into a sandbox that does not forgive it. None of this is a secret technique. It is the public constraint set.
The build itself. The community README notes that accessible tagged PDFs are off in the default build; you would recompile. Locales and codecs are stripped to fit. You are not running the Chrome your designer used. You are running a serverless fork that happens to print.
The 5 GB beta does not retire the problem
On 29 June 2026 Vercel published that Functions on Fluid compute can ship up to 5 GB uncompressed (https://vercel.com/changelog/vercel-functions-can-now-be-up-to-5-gb-in-package-size). It is a public beta. Standard path remains 250 MB. Large Functions need Fluid compute with Active CPU. They are not supported with Secure Compute or Static IPs. New projects after 30 June 2026 are enrolled; older projects opt in with VERCEL_SUPPORT_LARGE_FUNCTIONS=1. The troubleshooting note for the 250 MB error says the same.
A larger zip means you can sometimes skip the remote pack and ship a full browser. It does not remove cold start, memory, maxDuration, version pins, or the fact that a PDF route now owns a browser lifecycle. The November 2025 Vercel guide still leads with the 250 MB story and the min package. Treat the beta as an opt-in for oversized deps, not as a reason to put print in the web app.
Function browser vs POST /v1/pdf
The API side is one source field: html, url, markdown, or templateId. HTML is UTF-8 JSON, not Base64. response is binary (default), url (24-hour download), or async. printBackground defaults to true. waitUntil defaults to networkidle0. timeout max is 60 seconds. headerTemplate and footerTemplate are header HTML with pageNumber, totalPages, date, title. Private, loopback, and metadata hosts are rejected on url. That is the public contract, not a performance claim.
| Concern | Browser in the Vercel function | PDF API (RelayPDF) |
|---|---|---|
| Binary | You pack or fetch it | Already running |
| Next.js config | Externals, tracing, Node-only | fetch from a Route Handler |
| Version drift | Driver vs community browser build | One browser on the API |
| Cold path | Materialize on the instance | HTTP to a pool you do not run |
| Edge | Impossible | The handler can stay tiny |
| Print options | Whatever you pass locally | Documented options on /v1/pdf |
| Office / merge | More binaries | POST /v1/convert, POST /v1/pdf/merge |
| When it is right | Custom DevTools session | You need a PDF |
What the Route Handler should do
Keep auth, data loading, and HTML assembly in Next.js. Do not start a browser there. A handler that already has the invoice HTML can POST it and stream the bytes back. The Next.js-shaped version of this lives at /blog/html-to-pdf-nextjs. The product page is enough to start: https://relaypdf.com/html-to-pdf. Same fields from Node via the official SDK (fromHtml / fromUrl). The SDK does not change the engine. If you already have an in-process print script, the compare table is https://relaypdf.com/compare/puppeteer: managed browser, ready PDF and image endpoints, tools plus barcodes plus zip, wallet, CLI and MCP. You keep writing the HTML. You stop shipping Chrome.
curl -X POST https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{"html":"<h1>Invoice #1042</h1>","filename":"invoice.pdf"}'
--output invoice.pdf
When keeping a browser on Vercel is still the job
Stay in-process if you need a live DevTools protocol session against a page that does not exist until after a login dance you refuse to replay as HTML. Stay if the document must never leave the VPC and you have already accepted a container or a long-running Node, not a hobby function. Stay if you are generating one screenshot in a template Vercel already maintains and you like reading function logs at 2 a.m. Those are product choices. They are not the default for invoices. Self-hosting a browser somewhere that is not Vercel (Gotenberg, a VM, a container you own) is a different post. The failure mode here is specifically a browser inside the Next deploy.
Limits
RelayPDF timeout is 60 seconds of render, not an SDK HTTP timeout. render_failed is not billed. You cannot fetch private URLs. You cannot run this on Edge as a substitute for a browser. You cannot assume the @sparticuz/chromium build emits tagged PDFs. You cannot treat GitHub Releases as an SLA. Vercel duration and memory are plan-scoped; they are not infinite because Fluid compute exists. This piece does not invent cold-start milliseconds, cost-per-PDF on a function, or win rates.
Pricing (verified)
RelayPDF prepaid wallet (https://relaypdf.com/pricing, checked 23 Aug 2026): HTML, URL, Markdown PDF or screenshot $0.015; template PDF $0.015; AI generate $0.05; LibreOffice $0.04; wkhtmltopdf $0.025; tools $0.005. Failures never billed. Credit does not expire. $5 signup credit, no card. Pack bonuses 5/6/10/15 percent. Vercel function GB-hours and Active CPU are on Vercel bill; they are not converted into a fake per-PDF number here.
Ship the PDF, not the browser
If the search was vercel puppeteer pdf, the recurring outage is the layer, not your CSS. Put the renderer next to the documents, not next to getServerSideProps. Product: https://relaypdf.com/html-to-pdf. Next.js route walkthrough: https://relaypdf.com/blog/html-to-pdf-nextjs. Options: https://relaypdf.com/docs/options. Driver compare: https://relaypdf.com/compare/puppeteer.