Comparison

urltodoc vs running Puppeteer yourself

Puppeteer is excellent software. We use it too. The question is not whether it works — it is whether you want to operate it, in production, on the first of every month.

The forty lines

Launch a browser, open a page, goto, pdf(), close. It works on your laptop in an afternoon, and that is genuinely the right call for an internal script.

The next four thousand

A browser pool, memory ceilings, zombie processes, a font layer in the image, retries for pages that were still loading, and a Chromium upgrade policy nobody volunteered for.

Concern Self-hosted Puppeteer urltodoc
Time to first PDF Self-hosted PuppeteerAn afternoon locally, then days to make the container behave. urltodocOne POST. Four minutes including reading the docs.
Memory Self-hosted PuppeteerEach Chromium wants ~1GB warm. Serverless kills it, containers OOM under bursts. urltodocNot your problem. A warm pool absorbs the burst.
Fonts Self-hosted PuppeteerWhatever the base image ships. Usually nothing, so brand faces and CJK silently fall back. urltodoc1,100 fonts bundled, emoji and CJK included, plus your licensed uploads.
Determinism Self-hosted PuppeteerA Chromium bump can shift page breaks in documents you already archived. urltodocVersion-pinned engine per project. Upgrades are opt-in.
Waiting for the page Self-hosted Puppeteernetworkidle0, then a sleep, then a retry, then a longer sleep. urltodocwait_for a selector, an event, or network idle, with a real timeout error.
Headers, footers, merging Self-hosted PuppeteerHand-built templates, then a second library to glue PDFs together. urltodocheader_html, footer_html, merge and table_of_contents as fields.
Bursts on the 1st Self-hosted PuppeteerYou size the fleet for the worst hour of the month and pay for it all month. urltodocBatch and async endpoints, priority queue, elastic capacity.
Observability Self-hosted PuppeteerLogs, if you wrote them. Reproducing a bad render means finding the inputs. urltodocEvery render on the dashboard with its exact request, replayable.
Cost at 30k / month Self-hosted PuppeteerInstances, a queue, storage, plus the engineer who owns it. urltodoc$79 on Growth, storage and CDN included.
Who fixes it at 3am Self-hosted PuppeteerYou. urltodocUs — and a status page you can point at.

You already run a browser fleet for end-to-end tests, and PDFs are a small extra job on infrastructure that exists and has an owner.

Your renders must never leave your network, and a vendor DPA plus regional pinning is not enough for your compliance team.

You need deep browser automation — multi-step flows, form filling, intercepting requests — rather than a document at the end of it.

You render a handful of documents a month and a cron job on one box is genuinely fine.

Keep your Puppeteer path behind a flag for a week and compare checksums. If a document differs, send it to us — that is a bug we want.

- const browser = await puppeteer.launch({ - args: ["--no-sandbox", "--disable-dev-shm-usage"] - }); - const page = await browser.newPage(); - await page.goto(url, { waitUntil: "networkidle0" }); - await page.evaluateHandle("document.fonts.ready"); - const pdf = await page.pdf({ format: "A4", printBackground: true }); - await browser.close(); + const pdf = await client.render({ + url, + format: "A4", + print_background: true, + wait_for: "networkidle" + });

Run both for a week.

Compare the output, then delete the launcher. 100 free credits is enough to test the whole path.