The Puppeteer pipeline
A typical Puppeteer PDF generation setup looks like this:- Chrome dependency. You need Chromium installed in your environment. Docker images with Chromium are 400MB+.
- Startup time. Launching Chrome takes 500ms-2s. Even with browser pooling, each page creation adds latency.
- Memory. Each Chrome process uses 50-200MB of RAM. Under load, memory pressure causes crashes.
- Page break fragility. CSS
page-break-inside: avoidis a suggestion, not a guarantee. Chrome sometimes ignores it, especially with complex layouts. - No repeating headers. There is no CSS mechanism to repeat table headers on every page. Chrome’s
theadrepetition is inconsistent. - Security surface. Running a headless browser in production introduces a class of vulnerabilities (navigation to malicious URLs, resource exhaustion, sandbox escapes).
The Forme equivalent
renderDocument() call runs a WASM module in-process and returns PDF bytes.
Performance comparison
| Metric | Puppeteer | Forme |
|---|---|---|
| Simple invoice | ~1-3 seconds | ~5-15 milliseconds |
| Memory per render | 50-200MB (Chrome process) | ~2-5MB (WASM) |
| Docker image size | 400MB+ (with Chromium) | Same as your Node.js base image |
| Concurrent renders | Limited by Chrome processes | Limited by CPU cores |
| Cold start (serverless) | 3-10 seconds | Under 100 milliseconds |
| Dependencies | Chromium binary | None (WASM) |
Step-by-step migration
1. Install Forme
2. Convert your HTML template to JSX
Map your HTML structure to Forme components:| HTML | Forme |
|---|---|
<div> | <View> |
<p>, <span>, <h1> | <Text> with appropriate styles |
<table> | <Table> + <Row> + <Cell> |
<img> | <Image> |
| CSS classes | Inline style objects |
3. Replace the render call
Before:4. Remove Puppeteer dependencies
When Puppeteer is still the right choice
Forme is not a drop-in replacement for every Puppeteer use case. Keep Puppeteer if:- You are rendering existing HTML/CSS you do not control. If you receive HTML from a CMS, email template system, or third-party API and need to convert it to PDF, Puppeteer renders arbitrary HTML. Forme requires you to rewrite the template in JSX.
-
You need full CSS support. Forme supports a subset of CSS (flexbox, basic typography, borders, backgrounds). If your templates rely on CSS Grid, CSS animations,
position: absolute, or other advanced CSS features, Puppeteer handles all of them. - You need SVG rendering. Forme does not render SVG. If your PDFs contain charts from D3, Chart.js, or other SVG-based libraries, Puppeteer renders them natively.
- You need JavaScript execution. If your template includes client-side JavaScript that modifies the DOM before rendering (e.g., charting libraries, dynamic calculations), Puppeteer executes it. Forme templates are static at render time.
- You need screenshots, not PDFs. Puppeteer captures screenshots of web pages. Forme only produces PDFs.
Common patterns
Conditional page breaks
Puppeteer:@media print { .section { page-break-before: always; } } (unreliable)
Forme:
Repeating headers
Puppeteer: Use<thead> and hope Chrome repeats it (it often does not)
Forme:
Page numbers
Puppeteer: CSS@page { @bottom-center { content: counter(page); } } (limited styling)
Forme: