บล็อก

gPdf vs Puppeteer: เมื่อ Chromium 800 MB เป็นคำตอบที่ผิด

Puppeteer แปลง web page เป็น PDF ได้ทุกแบบ แต่ในงานเอกสารจำนวนมาก คุณกำลังจ่ายให้ headless browser ที่ไม่ได้ใช้จริง การเปรียบเทียบสำหรับปี 2026

ถ้าวันนี้คุณค้นหา “Puppeteer PDF alternative” คำถามจริงอาจเป็น:

“ทำไม serverless function ของฉันต้อง cold start 2 seconds และใช้ RAM 900 MB แค่เพื่อพิมพ์ invoice หนึ่งใบ?”

Puppeteer เป็น tool ที่ยอดเยี่ยม แต่สำหรับงานที่หลายทีมใช้มันทำ คือแปลง structured data เป็น predictable PDF มันใหญ่เกินไป บทความนี้สำหรับทีมที่กำลังจะส่ง Puppeteer เข้า production แต่เริ่มสงสัยว่ามีทางที่สมเหตุสมผลกว่าไหม

คุณ ship อะไรจริงๆ กับ Puppeteer

เมื่อ npm install puppeteer คุณดาวน์โหลด Chromium build ประมาณ 170 MB ก่อนนับ transitive deps ตอน runtime, headless Chromium มักต้องใช้ 600-900 MB resident memory ต่อ page render และ 1-2 seconds cold start เพื่อเริ่ม browser ทุก render ต้อง:

  1. Boot browser process หรือ reuse pool
  2. เปิด tab ใหม่
  3. Navigate ไปยัง HTML / URL
  4. รอ domcontentloaded และมักต้องรอ fonts, images, web components
  5. เรียก page.pdf() เพื่อ serialize painted page ผ่าน Chromium PDF engine
  6. ปิด tab

นี่คือ whole-web-platform tax คุณจ่ายภาษีนี้เท่ากัน ไม่ว่าจะเป็น legal contract 90 หน้า หรือ shipping label หน้าเดียวที่มีห้าบรรทัด

ถ้า input เป็น HTML จริงๆ และต้องใช้ CSS layout, JavaScript, web fonts ภาษีนี้สมเหตุสมผล แต่สำหรับ invoices, labels, receipts, tickets, statements และ certificates มักเกินจำเป็น

Puppeteer ชนะตรงไหน

ต้องซื่อตรงกับ workload ก่อน migrate:

  • 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 ทั้งหมด

ถ้าสองข้อข้างบนตรงกับงานจริง อย่าเปลี่ยน 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 พิมพ์ ▢▢▢▢ 2025年第3四半期. Customer escalates. ทีมใช้ sprint ไปกับ Dockerfile fonts และ CSS fallback

แค่ NotoSans CJK ก็เพิ่ม ~50 MB ให้ image แล้ว Global Noto fallback set อาจเพิ่ม ~250 MB คุณจ่ายทั้ง Chromium และชุด font ขนาดใหญ่เพื่อ invoice ภาษาญี่ปุ่นหนึ่งใบ

Determinism

Puppeteer renders ไม่ byte-identical ระหว่าง Chromium versions Patch upgrade อาจเปลี่ยน kerning, font baselines หรือ page breaks ถ้าคุณมี PDF diff tests ทุก Chromium update จะกลายเป็นการสืบสวน

Render-time JavaScript

HTML “static” ก็ยังต้อง 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 ที่ compile เป็น WebAssembly emit PDF โดยตรง ไม่มี browser, ไม่มี DOM, ไม่มี JavaScript layout pass

นี่จำกัดสำหรับปัญหาที่เป็น HTML จริง แต่สำหรับ invoice / label / receipt / statement / certificate โมเดล JSON-first มักเหมาะกว่า:

  • Data ของคุณ structured อยู่แล้ว. Invoice มักเป็น object เช่น { 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 เดิม

gPdf render one-page invoice มักอยู่ที่ 3-5 ms p50 wall-clock บน edge เร็วกว่า Puppeteer warm path ประมาณ สอง orders และเร็วกว่า cold path ประมาณ สาม orders

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

ถ้าคอลัมน์ขวาชนะมากกว่าสามแถว savings ไม่ใช่เรื่องเล็ก

เปรียบเทียบจริง: 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)

แถวสุดท้ายมักทำให้คนแปลกใจ แต่มันไม่ใช่ teaser price มันเป็นโครงสร้าง เพราะเราไม่ต้อง amortize Chromium boot, browser memory หรือ container cold starts

“แต่ $5/100,000 หน้า ฟังดูถูกเกินไป catch คืออะไร?”

catch คือเราไม่ได้รัน browser ค่าใช้จ่ายของ binary renderer บน warm V8 isolate คือ milliseconds CPU และ kilobytes memory ถ้าคิดราคาแบบ Puppeteer ก็เท่ากับเก็บเงินสำหรับ infrastructure ที่เราไม่ได้รัน

เมื่อไหร่ยังควรเลือก Puppeteer

คำตอบที่ซื่อสัตย์ไม่ใช่ “ใช้ gPdf” เสมอ:

  1. Puppeteer อยู่ใน production แล้วและทำงานดี. อย่า migrate เพื่อ migrate ประเมิน gPdf เมื่อ Puppeteer เริ่มเจ็บ เช่น compute bill เกิน 400 USD/เดือน หรือ cold start ทำ SLA พัง
  2. Documents เป็น existing webpages จริงๆ. Report 60 หน้า user-generated พร้อม charts และ dynamic content ไม่ใช่ JSON migration แต่เป็น redesign
  3. ต้องการ pixel-perfect parity กับ web preview. บาง workflow ต้องใช้ Chromium renderer ทั้งสองฝั่ง

ถ้าไม่ใช่กรณีเหล่านี้ ตัวเลขชัดเจน: deploy เล็กลง, latency ต่ำลง, bill ต่ำลง, output byte-identical และปัญหา fonts น้อยลง

วิธี migrate workload จริง

โดยมากเป็น spike 1-2 วันต่อ document type:

  1. เลือก document หนึ่งแบบ เริ่มจาก volume สูงสุด ไม่ใช่ซับซ้อนที่สุด
  2. Map logical sections ของ HTML template ไปยัง gPdf JSON elements (text, box, table, barcode, image)
  3. Iterate ใน Playground ด้วย DocumentRequest จริง
  4. เขียน mapper เล็กๆ จาก data-shape เดิมให้ emit JSON
  5. ทำ A/B กับ Puppeteer endpoint หนึ่งสัปดาห์ Diff PDFs แล้วตัดสินใจ

ทีมส่วนใหญ่เข้าใจ JSON model ภายในวันเดียว สิ่งยากไม่ใช่ tool ใหม่ แต่คือการแกะ HTML/CSS gymnastics ที่สะสมใน template เดิม

TL;DR

Puppeteer เป็นคำตอบที่ถูกสำหรับ web pages แต่สำหรับ documents คุณจ่าย tax 100-200× ต่อ render เพื่อเลี่ยงต้นทุนเล็กๆ ครั้งเดียวในการอธิบาย document เป็น data ถ้า fleet ของคุณสร้าง invoices, labels, receipts, statements, tickets หรือ “same shape, different values” renderer แบบ edge-native อย่าง gPdf จะเร็วกว่า เล็กกว่า ถูกกว่า และ deterministic กว่า

ลองใน Playground: edge worker จริง ไม่ต้อง signup และ response ใน browser ต่ำกว่า 5 ms