Bugün “Puppeteer PDF alternative” diye aradıysanız, asıl soru muhtemelen şudur:
“Tek bir invoice yazdırmak için serverless function neden 2 seconds cold start alıyor ve 900 MB RAM kullanıyor?”
Puppeteer harika bir tool. Ama birçok ekibin yaptığı iş, yani structured data’yı predictable PDF’e çevirmek için fazla büyük. Bu yazı, Puppeteer’ı production’a taşımak üzereyken daha mantıklı bir seçenek olup olmadığını düşünen ekipler için.
Puppeteer ile aslında ne ship ediyorsunuz
npm install puppeteer çalıştığında, transitive deps hariç yaklaşık 170 MB Chromium build indirirsiniz. Runtime’da headless Chromium tek page render için 600-900 MB resident memory ve browser’ı kaldırmak için 1-2 seconds cold start ister. Her render:
- Browser process’i başlatır ya da pool reuse eder.
- Yeni tab açar.
- HTML / URL’ye navigate eder.
domcontentloadedve çoğu zaman fonts, images, web components bekler.page.pdf()çalıştırıp painted page’i Chromium PDF engine üzerinden serialize eder.- Tab’i kapatır.
Bu whole-web-platform tax’tir. 90-page legal contract için de, beş satırlık one-page shipping label için de aynı tax’i ödersiniz.
Input gerçekten HTML ise ve CSS layout, JavaScript, web fonts gerekiyorsa bu adil olabilir. Invoices, labels, receipts, tickets, statements ve certificates için çoğu zaman israftır.
Puppeteer nerede kazanır
Önce dürüst olun:
- Faithful HTML/CSS rendering. Design system HTML emit ediyor ve pixel-identical PDF gerekiyorsa Puppeteer en iyi seçenektir. Bu Chrome printing’dir.
- Web-platform features. SVG filters, CSS Grid edge cases, web components, JavaScript content, third-party iframes çalışır.
- Visual debugging. Render ortasında screenshot alabilir, headless mode’a DevTools açabilirsiniz.
- Zero translation step. Content zaten webpage ise schema mapping yoktur.
page.goto(url); await page.pdf()tüm pipeline’dır.
Bu maddelerden ikisi gerçek iş yükünüzü tarif ediyorsa, geçmeyin. Puppeteer doğru cevaptır.
Puppeteer nerede kaybeder
Diğer her yerde cost hızla birikir.
Serverless memory ve cold start
Puppeteer çalışan tipik Node 20 Lambda veya Cloudflare Container:
| 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 ayda 100.000 render yapıyorsa, hiçbir render JavaScript execution istemese bile her cold container için browser boot bedeli ödersiniz.
Container font tuzağı
Chromium default font set ile gelir; CJK, Cyrillic, Devanagari, Arabic ve pek çok glyph genelde eksiktir. Production’da şöyle görünür:
Tokyo office Q3 2025 invoice
▢▢▢▢ 2025年第3四半期basar. Customer escalates. Ekip bir sprint’i Dockerfile fonts ve CSS fallback debug’a harcar.
Sadece NotoSans CJK image’a ~50 MB ekler. Global Noto fallback set ~250 MB ekleyebilir. Tek bir Japanese invoice için Chromium ve dev bir font paketi taşırsınız.
Determinism
Puppeteer renders Chromium versions arasında byte-identical değildir. Patch upgrade kerning, font baselines veya page breaks değiştirebilir. PDF diff tests varsa her Chromium update küçük bir investigation olur.
Render-time JavaScript
“Static” HTML bile parse, layout, paint ve serialize edilir. Warm process’te bu 80-400 ms per page olabilir. Aynı one-page invoice JSON olarak binary renderer’a giderse 3-8 ms sürer.
gPdf nereye oturur
gPdf modeli tersine çevirir: belgeyi tarayıcının çizeceği HTML olarak değil, yapılandırılmış JSON (DocumentRequest) olarak tanımlarsınız. Rust renderer WebAssembly’ye derlenir ve PDF’i doğrudan üretir. Browser yok, DOM yok, JavaScript layout pass yok.
Gerçek HTML-shaped problems için sınırlayıcıdır. Ama invoice / label / receipt / statement / certificate sınıfında JSON-first model daha uygundur:
- Veri zaten yapılandırılmıştır. Invoice zaten genelde
{ customer, lines, totals, taxes, notes }objesidir. Onu HTML’e çevirip browser’a layout olarak tekrar okutmak istemezsiniz. - Layout contract olur.
font_size: 11her zaman 11 point,gap: 8her zaman 8 point demektir. - Çıktı byte-identical’dır. Same input → same bytes. PDF diff anlamlıdır.
- Cold start runtime startup’tır, browser boot değil. Cloudflare Workers’ta V8 isolate 5-20 ms içinde başlar; WASM module aynı isolate’ta hot kalır.
gPdf ile typical one-page invoice render edge’de 3-5 ms p50 wall-clock verir. Puppeteer warm path’ten yaklaşık iki order, cold path’ten üç order hızlıdır.
Decision matrix
| 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 |
Sağ kolon üçten fazla satırda kazanıyorsa savings küçük değildir.
Gerçek karşılaştırma: one-page invoice
Aynı content, paper size, NotoSans fonts ve 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 |
| Maliyet / 100.000 render | ~$240 (Lambda compute) | ~$5 (gPdf Basic plan) |
Son satır şaşırtır, ama teaser price değildir. Yapısaldır. Chromium boot, browser memory veya container cold starts amortize etmiyoruz.
“Ama $5/100.000 sayfa çok ucuz geliyor. Catch ne?”
Catch şu: browser çalıştırmıyoruz. Warm V8 isolate üstünde binary renderer’ın cost’u milliseconds CPU ve kilobytes memory’dir. Puppeteer fiyatı almak çalıştırmadığımız infrastructure için charge etmek olurdu.
Ne zaman yine Puppeteer seçilmeli
Dürüst cevap her zaman “gPdf kullan” değildir:
- Puppeteer production’da çalışıyor ve sorun çıkarmıyor. Sırf değiştirmek için migrate etmeyin. Compute bill 400 USD/ay üstüne çıktığında veya cold-start SLA bozduğunda gPdf’i değerlendirin.
- Documents gerçekten existing webpages. Charts ve dynamic content içeren user-generated 60-page report JSON migration değil redesign’dır.
- Web preview ile pixel-perfect parity gerekiyor. Bazı workflows iki tarafta da Chromium renderer ister.
Hiçbiri geçerli değilse matematik nettir: smaller deploy, lower latency, lower bill, byte-identical output ve daha az font-install drama.
Gerçek iş yükü nasıl taşınır
Genelde document type başına 1-2 day spike:
- Bir document seçin; en karmaşık olanı değil, en yüksek volume olanı.
- HTML template’in logical sections’ını gPdf JSON elements’e (
text,box,table,barcode,image) map edin. - Playground içinde gerçek
DocumentRequestile iterate edin. - Mevcut data-shape’ten JSON emit eden küçük mapper yazın.
- Bir hafta Puppeteer endpoint’e karşı A/B yapın. PDFs diff edin. Karar verin.
Çoğu ekip JSON model’i bir günde kavrar. Zor olan yeni tool değil, eski template’teki HTML/CSS gymnastics’i çözmektir.
TL;DR
Puppeteer web pages için doğru cevaptır. Documents için ise document’ı data olarak tanımlama şeklindeki küçük one-time cost’tan kaçmak için her render’da 100-200× tax ödersiniz. Fleet invoices, labels, receipts, statements veya “same shape, different values” documents render ediyorsa gPdf gibi edge-native renderer daha hızlı, küçük, ucuz ve deterministic olur.
Playground deneyin. Gerçek edge worker, signup yok, browser response under 5 ms.