Блог

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

Puppeteer делает PDF из любой web page, но часто вы платите за headless browser, который не нужен. Практичное сравнение PDF-стеков на 2026 год.

Если вы сегодня искали “Puppeteer PDF alternative”, реальный вопрос, вероятно, такой:

“Почему моя serverless function делает cold start 2 seconds и ест 900 MB RAM только ради одной invoice?”

Puppeteer — отличный инструмент. Но для задачи, где нужно превратить structured data в predictable PDF, он часто слишком тяжелый. Этот текст для команды, которая почти готова отправить Puppeteer в production и подозревает, что есть более разумный вариант.

Что вы на самом деле ship с Puppeteer

npm install puppeteer загружает около 170 MB Chromium еще до transitive deps. В runtime headless Chromium обычно требует 600-900 MB resident memory на один render страницы и 1-2 seconds cold start, чтобы поднять browser. Каждый render:

  1. Запускает browser process или берет его из pool.
  2. Открывает новую tab.
  3. Переходит на ваш HTML / URL.
  4. Ждет domcontentloaded, а часто fonts, images и web components.
  5. Выполняет page.pdf(), сериализуя painted page через Chromium PDF engine.
  6. Закрывает tab.

Это налог всей web platform. Вы платите его и за 90-page legal contract, и за one-page shipping label с пятью строками.

Для HTML-to-PDF, где действительно нужны CSS layout, JavaScript, web fonts и browser semantics, налог оправдан. Для invoices, labels, receipts, tickets, statements и certificates обычно нет.

Где Puppeteer выигрывает

Сначала честно проверьте это:

  • Точное HTML/CSS rendering. Если design system выдает HTML и нужен pixel-identical PDF, 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.
  • Нет translation step. Если content уже page, не нужен schema mapping. page.goto(url); await page.pdf() — вся pipeline.

Если два пункта описывают вашу нагрузку, не мигрируйте. Puppeteer правильный выбор.

Где Puppeteer проигрывает

Во всем остальном стоимость быстро растет.

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 fonts, но часто без 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 и огромный набор fonts ради одной японской invoice.

Determinism

Puppeteer renders не byte-identical между версиями Chromium. Patch upgrade может сдвинуть kerning, font baselines или page breaks. Если у вас PDF diff tests, каждый update Chromium превращается в расследование.

Render-time JavaScript

Даже “static” HTML нужно parse, layout, paint и serialize. На warm process это 80-400 ms на page, в основном layout. Тот же one-page invoice из JSON в binary renderer занимает 3-8 ms.

Где подходит gPdf

gPdf меняет модель: document описывается не как HTML для browser, а как structured JSON (DocumentRequest), после чего Rust renderer в WebAssembly напрямую emit PDF. Нет browser, DOM и JavaScript layout pass.

Это ограничение для настоящих HTML-задач. Но для invoice / label / receipt / statement / certificate JSON-first model обычно лучше:

  • Данные уже структурированы. Invoice обычно живет как { customer, lines, totals, taxes, notes }. Не нужно делать HTML, чтобы browser прочитал его обратно как layout.
  • Layout становится контрактом. font_size: 11 всегда 11 points, gap: 8 всегда 8 points.
  • Output byte-identical. Same input → same bytes. PDF diff становится надежным.
  • Cold start — runtime, а не 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. Это примерно на два порядка быстрее warm path Puppeteer и на три порядка быстрее 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 latency 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)

Последняя строка удивляет, но это не приманка. Разница структурная: мы не амортизируем 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 — существующие web pages. User-generated report на 60 pages с charts и dynamic content — это не JSON migration, а redesign.
  3. Нужна pixel-perfect parity с web preview. Некоторые workflows требуют Chromium renderer с обеих сторон.

Если это не про вас, математика простая: меньший deploy, ниже latency, ниже bill, byte-identical output и меньше проблем с fonts.

Как мигрировать реальную нагрузку

Обычно это 1-2 day spike на тип документа:

  1. Выберите один document: лучше самый массовый, а не самый сложный.
  2. Map logical sections HTML template в gPdf JSON elements (text, box, table, barcode, image).
  3. Итерируйте в Playground с реальным DocumentRequest.
  4. Напишите небольшой mapper, который emit JSON из вашей data-shape.
  5. Запустите A/B против Puppeteer endpoint на неделю. Diff PDFs. Решите.

Большинство команд понимают JSON model за день. Сложность не в новом tool, а в распутывании старой HTML/CSS гимнастики.

TL;DR

Puppeteer — правильный ответ для web pages. Для documents вы платите 100-200× tax на каждый render, чтобы избежать небольших разовых затрат на описание документа как data. Если система генерирует invoices, labels, receipts, statements или tickets с одинаковой формой и разными значениями, edge-native renderer вроде gPdf будет быстрее, меньше, дешевле и детерминированнее.

Попробуйте Playground: это реальный edge worker, без signup, с ответом в browser меньше 5 ms.