Блог

gPdf vs Puppeteer: коли 800 MB Chromium — неправильна відповідь

Puppeteer робить PDF з будь-якої вебсторінки, але часто ви платите за headless browser, який не потрібен. Практичне порівняння PDF-стеків на 2026.

Якщо ви сьогодні шукали “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:

  1. Запускає browser process або reuse pool.
  2. Відкриває новий tab.
  3. Переходить на HTML / URL.
  4. Чекає domcontentloaded, а часто fonts, images і web components.
  5. Виконує page.pdf(), serializing painted page через Chromium PDF engine.
  6. Закриває 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”:

  1. Puppeteer уже в production і працює. Не мігруйте заради міграції. Оцініть gPdf, коли Puppeteer почне боліти: compute bill понад 400 USD/міс. або cold start ламає SLA.
  2. Documents — це наявні вебсторінки. User-generated 60-page report із charts і dynamic content — не JSON migration, а redesign.
  3. Потрібна pixel-perfect parity з web preview. Деякі workflows потребують Chromium renderer з обох боків.

Якщо ні, математика проста: менший deploy, нижча latency, менший bill, byte-identical output і менше проблем із fonts.

Як мігрувати реальний workload

Зазвичай це 1-2 day spike на document type:

  1. Оберіть один document, краще з найбільшим volume, не найскладніший.
  2. Map logical sections HTML template у gPdf JSON elements (text, box, table, barcode, image).
  3. Ітеруйте в Playground з реальним DocumentRequest.
  4. Напишіть невеликий mapper з поточної data-shape у JSON.
  5. Запустіть 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.