Blog

gPdf vs Puppeteer: quando 800 MB di Chromium sono la risposta sbagliata

Puppeteer trasforma qualsiasi pagina web in PDF, ma spesso pagate per un browser senza interfaccia grafica che usate solo in parte. Un confronto pragmatico per scegliere uno stack PDF nel 2026.

Se oggi avete cercato “Puppeteer PDF alternative”, la domanda vera probabilmente è una variante di questa:

“Perché la mia funzione serverless impiega 2 secondi di cold start e usa 900 MB di RAM solo per stampare una fattura?”

Puppeteer è uno strumento eccellente. È anche sovradimensionato per il lavoro per cui molti team lo usano: trasformare dati strutturati in un PDF prevedibile. Questo confronto è per il team che sta per portare Puppeteer in produzione e sospetta che esista una scelta più ragionevole.

Vedremo dove Puppeteer merita il suo peso, dove no, e com’è davvero la matrice dei compromessi nel 2026.

Cosa stai davvero distribuendo con Puppeteer

Con npm install puppeteer scaricate circa 170 MB di Chromium prima ancora delle dipendenze transitive. A runtime, Chromium senza interfaccia grafica richiede spesso 600–900 MB di memoria residente per il render di una singola pagina e 1–2 secondi di cold start per avviare il browser. Ogni render deve:

  1. Avviare il processo browser, o riusare un pool.
  2. Aprire una nuova scheda.
  3. Navigare al tuo HTML / URL.
  4. Attendere domcontentloaded, e spesso font, immagini e web component.
  5. Eseguire page.pdf(), serializzando la pagina dipinta tramite il motore PDF di Chromium.
  6. Chiudere la scheda.

Questa è la tassa dell’intera piattaforma web. La pagate sia per un contratto legale di 90 pagine con grafici SVG incorporati, sia per un’etichetta di spedizione di una pagina con cinque righe di testo.

Per i casi HTML-to-PDF in cui l’input ha davvero bisogno di layout CSS, contenuto guidato da JavaScript, web font e tutto il resto della piattaforma web, quella tassa è corretta. Per tutto il resto, cioè fatture, etichette, ricevute, biglietti, estratti conto e certificati, significa bruciare denaro.

Dove Puppeteer vince

Siate onesti su questo punto prima di migrare, altrimenti il team rimetterà in discussione la decisione più avanti:

  • Rendering HTML/CSS fedele. Se il vostro design system emette HTML e volete PDF pixel-identici a quell’HTML, Puppeteer è difficilmente battibile. È Chrome che stampa.
  • Funzionalità della piattaforma web. SVG con filtri, casi limite di CSS Grid, web component, contenuto valutato da JavaScript e iframe di terze parti funzionano.
  • Debug visuale. Potete fare uno screenshot a metà render, aprire DevTools contro la modalità senza interfaccia grafica e vedere esattamente ciò che vede il motore.
  • Nessun passaggio di traduzione. Se il contenuto è già una pagina web, non c’è mapping di schema. page.goto(url); await page.pdf() è l’intera pipeline.

Se due di questi punti descrivono il vostro carico reale, non cambiate. Puppeteer è la risposta corretta.

Dove Puppeteer perde

Nel resto dei casi, il costo si accumula in fretta.

Memoria e cold start in serverless

Un tipico Node 20 Lambda o Cloudflare Container con Puppeteer:

Metrica Valore tipico
Dimensione immagine container 250–400 MB (Chromium + Node + il vostro codice)
Tempo di cold start 1,8–2,5 secondi
RAM warm per render 600–900 MB
Render concorrenti per istanza da 1 GB 1 (a volte 2 se le pagine sono minuscole)

Se il vostro servizio fatture gestisce 100.000 render al mese, pagate l’avvio del browser in ogni container freddo anche se nessuno di quei render richiedeva esecuzione JavaScript.

La trappola dei font nei container

Chromium include un set di font di default che spesso non copre CJK, cirillico, devanagari, arabo e una lunga coda di glifi specifici per script. In produzione lo scoprite così:

La fattura Q3 2025 dell’ufficio di Tokyo stampa ▢▢▢▢ 2025年第3四半期. Il cliente apre un’escalation. Il team passa uno sprint a debuggare installazioni di font nel Dockerfile e fallback CSS.

Solo NotoSans CJK aggiunge ~50 MB all’immagine. Un set globale di fallback Noto può aggiungere ~250 MB. State pagando Chromium e una cattedrale di font solo per stampare una fattura giapponese.

Determinismo

I render Puppeteer non sono byte-identici tra versioni di Chromium. Un aggiornamento patch può spostare leggermente kerning, baseline dei font o interruzioni pagina. Se avete una suite di test che confronta i PDF, e dovreste averla, ogni aggiornamento di Chromium diventa una piccola indagine: quale resa è cambiata, ed era voluto?

JavaScript al momento del render

Anche una pagina HTML “statica” deve essere parsata, calcolata nel layout, dipinta e serializzata. Su processo caldo sono empiricamente 80–400 ms per pagina. La stessa fattura di una pagina, inviata come JSON a un motore binario, richiede 3–8 ms.

Dove entra gPdf

gPdf ribalta il modello: invece di descrivere il documento come HTML e chiedere a un browser di dipingerlo, lo descrivete come JSON strutturato (DocumentRequest) e un motore Rust compilato in WebAssembly emette direttamente il PDF. Niente browser. Niente DOM. Niente passaggio di layout JavaScript.

È restrittivo per problemi davvero modellati su HTML. Ma per la classe di documenti fattura / etichetta / ricevuta / estratto conto / certificato, il modello JSON-first è spesso più adatto:

  • I dati sono già strutturati. Una fattura vive spesso come { customer, lines, totals, taxes, notes }. Non volete prima renderizzarla in HTML e poi chiedere a un browser di rileggerla come layout. Volete andare direttamente dai dati al PDF.
  • Il layout diventa un contratto. Quando font_size: 11 significa sempre 11 punti e gap: 8 significa sempre 8 punti, due engineer che rivedono una PR vedono esattamente lo stesso output. Non c’è divario interpretativo da display: flex.
  • L’output è byte-identico. Stesso input → stessi byte. Potete fare git diff tra due PDF e vedere solo ciò che è cambiato.
  • Il cold start è l’avvio del runtime, non del browser. Un isolate V8 su Cloudflare Workers si inizializza in 5–20 ms. Il modulo WASM resta caldo in memoria tra invocazioni nello stesso isolate.

Un render gPdf tipico di una fattura di una pagina sta a 3–5 ms p50 wall-clock all’edge, servito dalla colo Cloudflare raggiunta dall’utente. È circa due ordini di grandezza più veloce del percorso warm di Puppeteer e tre ordini di grandezza più veloce del percorso cold.

Matrice decisionale

Carico Usate Puppeteer Usate gPdf
Report HTML esistente → PDF ✅ prima scelta ⚠️ richiede riscrittura
Fatture, estratti conto, ricevute ⚠️ strumento pesante ✅ prima scelta
Etichette di spedizione con codici a barre ❌ da evitare (problemi di font) ✅ prima scelta
E-invoice (Factur-X / ZUGFeRD / EN 16931) ❌ nessun supporto integrato ✅ integrato
Archiviazione a lungo termine PDF/A ⚠️ richiede un passaggio Ghostscript ✅ profili integrati
Mockup di design system pixel-fedeli ✅ prima scelta ❌ strumento sbagliato
Grafici che richiedono D3 / Recharts reali ✅ prima scelta ❌ strumento sbagliato
Biglietti, certificati, badge nominali ⚠️ sovradimensionato ✅ prima scelta
Qualsiasi cosa richieda JavaScript al momento del render ✅ unica scelta ❌ strumento sbagliato

Se la colonna destra vince in più di tre righe, il risparmio non è sottile.

Confronto reale: fattura di una pagina

Stesso contenuto. Stesso formato carta. Stessi font NotoSans. Stesso profilo di output PDF/A-3b.

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
Costo / 100.000 render ~240 USD (calcolo Lambda) ~5 USD (piano gPdf Basic)

L’ultima riga sorprende spesso. Il divario di costo è reale e non è un prezzo-esca: è strutturale. Non dobbiamo ammortizzare l’avvio di Chromium, la memoria del browser o i cold start dei container, quindi il costo unitario per render è davvero minuscolo.

“Ma $5/100.000 pagine sembra troppo poco. Dov’è il trucco?”

Il trucco è esattamente che non spediamo un browser. Il costo di un motore binario su isolate V8 caldo è fatto di millisecondi di CPU e kilobyte di memoria. Applicare prezzi da Puppeteer significherebbe far pagare infrastruttura che non gestiamo.

Quando scegliere ancora Puppeteer

Saremmo i peggiori interlocutori possibili se la nostra risposta fosse sempre “usate gPdf”. Non lo è. I casi onesti:

  1. Puppeteer è già in produzione e funziona. Non migrate per sport. Il momento giusto per valutare gPdf è quando Puppeteer inizia a fare male, di solito quando la bolletta di calcolo supera 400 USD/mese o quando il cold start rompe qualcosa a valle.
  2. I documenti sono pagine web esistenti, punto. Un report generato dagli utenti di 60 pagine, stilizzato dal vostro design system, con grafici annidati e contenuto dinamico, non è una migrazione JSON. È una riprogettazione.
  3. Serve parità pixel-perfect con un’anteprima web. Alcuni processi, per esempio “ciò che vedi nell’editor è ciò che stampi”, hanno davvero bisogno che Chromium sia il motore su entrambi i lati.

Se nessuno di questi casi si applica, il conto è semplice: deploy più piccolo, latenza più bassa, spesa minore, output byte-identico e niente drammi di font installati nel container.

Come migrare un workload reale

Se siete abbastanza convinti da provare, la migrazione è di solito uno spike di 1–2 giorni per tipo di documento, non una nuova architettura:

  1. Scegliete un documento: partite da quello con il volume più alto, non dal più complesso.
  2. Mappate le sezioni logiche del template HTML sugli elementi JSON di gPdf (text, box, table, barcode, image).
  3. Iterate nel Playground con un vero DocumentRequest finché l’output combacia.
  4. Collegate la vostra forma dati esistente a una piccola funzione mapper che emette JSON.
  5. Eseguite un A/B del nuovo servizio contro quello Puppeteer per una settimana. Confrontate i PDF. Decidete.

La maggior parte dei team entra nel modello JSON in un giorno. La parte difficile non è il nuovo strumento: è districare le acrobazie HTML/CSS accumulate nel vecchio template.

TL;DR

Puppeteer è la risposta giusta per le pagine web. Per i documenti, pagate una tassa 100–200× a ogni render per evitare il piccolo costo una tantum di descrivere il documento come dati. Se il vostro parco documenti genera fatture, etichette, ricevute, estratti conto, biglietti o qualunque output con “stessa forma ogni volta, valori diversi”, un motore edge-native come gPdf sarà misurabilmente più veloce, più piccolo, più economico e più deterministico.

Provatelo nel Playground: è un vero edge worker, senza registrazione, con risposta nel browser in meno di 5 ms.