Если вы сегодня искали “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:
- Запускает browser process или берет его из pool.
- Открывает новую tab.
- Переходит на ваш HTML / URL.
- Ждет
domcontentloaded, а часто fonts, images и web components. - Выполняет
page.pdf(), сериализуя painted page через Chromium PDF engine. - Закрывает 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”:
- Puppeteer уже в production и работает. Не мигрируйте ради миграции. Оцените gPdf, когда Puppeteer начнет болеть: compute bill выше 400 USD/мес. или cold-start ломает SLA.
- Ваши documents — существующие web pages. User-generated report на 60 pages с charts и dynamic content — это не JSON migration, а redesign.
- Нужна pixel-perfect parity с web preview. Некоторые workflows требуют Chromium renderer с обеих сторон.
Если это не про вас, математика простая: меньший deploy, ниже latency, ниже bill, byte-identical output и меньше проблем с fonts.
Как мигрировать реальную нагрузку
Обычно это 1-2 day spike на тип документа:
- Выберите один document: лучше самый массовый, а не самый сложный.
- Map logical sections HTML template в gPdf JSON elements (
text,box,table,barcode,image). - Итерируйте в Playground с реальным
DocumentRequest. - Напишите небольшой mapper, который emit JSON из вашей data-shape.
- Запустите 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.