Якщо ви сьогодні шукали “Puppeteer PDF alternative”, справжнє питання, ймовірно, таке:
“Чому моя serverless function робить cold start 2 seconds і використовує 900 MB RAM лише для друку одного invoice?”
Puppeteer — чудовий tool. Але для роботи, яку багато команд ним роблять, тобто перетворення структурованих даних на predictable PDF, він надто важкий. Це порівняння для команди, яка майже готова нести Puppeteer у production, але шукає розумнішу опцію.
Що ви насправді ship з Puppeteer
npm install puppeteer завантажує близько 170 MB Chromium ще до transitive deps. У runtime headless Chromium зазвичай потребує 600-900 MB resident memory для одного page render і 1-2 seconds cold start, щоб підняти browser. Кожен render:
- Запускає browser process або reuse pool.
- Відкриває новий tab.
- Переходить на HTML / URL.
- Чекає
domcontentloaded, а часто fonts, images і web components. - Виконує
page.pdf(), serializing painted page через Chromium PDF engine. - Закриває tab.
Це whole-web-platform tax. Ви платите його і за 90-page legal contract, і за one-page shipping label з п’ятьма рядками.
Коли input справді HTML і потребує CSS layout, JavaScript, web fonts, цей tax чесний. Для invoices, labels, receipts, tickets, statements і certificates зазвичай ні.
Де Puppeteer виграє
Спершу чесно перевірте:
- Faithful HTML/CSS rendering. Якщо design system emit-ить HTML і PDF має бути pixel-identical, Puppeteer важко замінити. Це Chrome printing.
- Web-platform features. SVG filters, CSS Grid edge cases, web components, JavaScript content і third-party iframes працюють.
- Visual debugging. Можна зробити screenshot під час render і відкрити DevTools до headless mode.
- Zero translation step. Якщо content уже webpage, schema mapping не потрібен.
page.goto(url); await page.pdf()— це вся pipeline.
Якщо два пункти описують ваш workload, не мігруйте. Puppeteer правильний.
Де Puppeteer програє
В інших випадках cost швидко росте.
Memory і cold start у serverless
Типовий Node 20 Lambda або Cloudflare Container із Puppeteer:
| Metric | Typical value |
|---|---|
| Container image size | 250-400 MB (Chromium + Node + your code) |
| Cold-start time | 1.8 - 2.5 seconds |
| Warm RAM per render | 600 - 900 MB |
| Concurrent renders per 1 GB instance | 1 (sometimes 2 if pages are tiny) |
Якщо invoice service робить 100 000 рендерів на місяць, ви платите за browser boot у кожному cold container, хоча жоден render не потребував JavaScript execution.
Пастка fonts у containers
Chromium має default font set, але часто бракує CJK, Cyrillic, Devanagari, Arabic і багатьох glyphs. У production це виглядає так:
Invoice Q3 2025 для Tokyo office друкує
▢▢▢▢ 2025年第3四半期. Customer escalates. Команда витрачає sprint на Dockerfile fonts і CSS fallback.
Один NotoSans CJK додає ~50 MB до image. Global Noto fallback set може додати ~250 MB. Ви платите за Chromium і величезний font bundle заради однієї японської invoice.
Determinism
Puppeteer renders не byte-identical між версіями Chromium. Patch upgrade може зрушити kerning, font baselines або page breaks. Якщо є PDF diff tests, кожен Chromium update стає розслідуванням.
Render-time JavaScript
Навіть “static” HTML треба parse, layout, paint і serialize. На warm process це 80-400 ms на page. Та сама one-page invoice з JSON у binary renderer займає 3-8 ms.
Де підходить gPdf
gPdf перевертає модель: document описується не як HTML для browser, а як структурований JSON (DocumentRequest). Rust renderer, скомпільований у WebAssembly, напряму emit-ить PDF. Немає browser, DOM і JavaScript layout pass.
Це обмеження для справжніх HTML-shaped проблем. Але для invoice / label / receipt / statement / certificate JSON-first model зазвичай кращий:
- Data вже structured. Invoice часто існує як
{ customer, lines, totals, taxes, notes }. Не потрібно робити HTML, щоб browser знову прочитав layout. - Layout стає contract.
font_size: 11завжди 11 points,gap: 8завжди 8 points. - Output byte-identical. Same input → same bytes. PDF diff стає корисним.
- Cold start — runtime startup, не browser boot. V8 isolate у Cloudflare Workers стартує за 5-20 ms, а WASM module лишається hot у тому ж isolate.
Типовий render one-page invoice у gPdf дає 3-5 ms p50 wall-clock на edge. Це приблизно на два порядки швидше за Puppeteer warm path і на три порядки швидше за cold path.
Матриця вибору
| Workload | Use Puppeteer | Use gPdf |
|---|---|---|
| Existing HTML report → PDF | ✅ first choice | ⚠️ requires rewrite |
| Invoices, statements, receipts | ⚠️ heavy hammer | ✅ first choice |
| Shipping labels with barcodes | ❌ avoid (font issues) | ✅ first choice |
| E-invoice (Factur-X / ZUGFeRD / EN 16931) | ❌ no built-in support | ✅ built-in |
| PDF/A long-term archival | ⚠️ needs Ghostscript pass | ✅ built-in profiles |
| Pixel-faithful design system mockups | ✅ first choice | ❌ wrong tool |
| Charts that need real D3 / Recharts | ✅ first choice | ❌ wrong tool |
| Tickets, certificates, name-tags | ⚠️ overkill | ✅ first choice |
| Anything that needs JavaScript at render time | ✅ only choice | ❌ wrong tool |
Якщо права колонка виграє більше ніж у трьох рядках, економія не дрібна.
Реальне порівняння: one-page invoice
Той самий content, paper size, NotoSans fonts і PDF/A-3b profile:
| Puppeteer (warm Lambda, 1 GB) | gPdf (warm Cloudflare Worker) | |
|---|---|---|
| p50 latency | 180 ms | 3.4 ms |
| затримка p99 | 420 ms | 8 ms |
| Cold-start penalty | +1800 ms first render | +12 ms first render |
| Memory at peak | 720 MB | 18 MB |
| Image / module size | 280 MB | 4.5 MB |
| CJK glyphs | ❌ unless explicit install | ✅ embedded NotoSans CJK |
| Вартість / 100 000 рендерів | ~$240 (Lambda compute) | ~$5 (gPdf Basic plan) |
Останній рядок дивує, але це не приманка price. Це структура: ми не амортизуємо Chromium boot, browser memory чи container cold starts.
“Але $5/100 000 сторінок звучить занадто дешево. Де підступ?”
Підступ у тому, що ми не запускаємо browser. Binary renderer на warm V8 isolate коштує milliseconds CPU і kilobytes memory. Ціна Puppeteer означала б оплату infrastructure, якої ми не запускаємо.
Коли все ще обрати Puppeteer
Чесна відповідь не завжди “використовуйте gPdf”:
- Puppeteer уже в production і працює. Не мігруйте заради міграції. Оцініть gPdf, коли Puppeteer почне боліти: compute bill понад 400 USD/міс. або cold start ламає SLA.
- Documents — це наявні вебсторінки. User-generated 60-page report із charts і dynamic content — не JSON migration, а redesign.
- Потрібна pixel-perfect parity з web preview. Деякі workflows потребують Chromium renderer з обох боків.
Якщо ні, математика проста: менший deploy, нижча latency, менший bill, byte-identical output і менше проблем із fonts.
Як мігрувати реальний workload
Зазвичай це 1-2 day spike на document type:
- Оберіть один document, краще з найбільшим volume, не найскладніший.
- Map logical sections HTML template у gPdf JSON elements (
text,box,table,barcode,image). - Ітеруйте в Playground з реальним
DocumentRequest. - Напишіть невеликий mapper з поточної data-shape у JSON.
- Запустіть A/B проти Puppeteer endpoint на тиждень. Diff PDFs. Вирішіть.
Більшість команд розуміє JSON model за день. Складність не в новому tool, а в розплутуванні старої HTML/CSS гімнастики template.
TL;DR
Puppeteer — правильна відповідь для вебсторінок. Для documents ви платите 100-200× tax на кожен render, щоб уникнути малого одноразового cost описати document як data. Якщо fleet генерує invoices, labels, receipts, statements, tickets або “same shape, different values”, edge-native renderer як gPdf буде швидшим, меншим, дешевшим і deterministic.
Спробуйте Playground: реальний edge worker, без signup, response у browser менше 5 ms.