Common problems Forme solves
Puppeteer PDF generation is slow. Launching Chrome, creating a page, setting HTML content, and rendering to PDF typically takes 1-3 seconds. Forme renders the same document in 5-15ms because there is no browser to start. Chrome uses too much memory. Each Chrome process consumes 50-200MB of RAM. Under concurrent load, memory pressure leads to crashes and OOM kills. Forme uses 2-5MB per render. CSS page breaks are unreliable.page-break-inside: avoid and break-before: always are hints that Chrome frequently ignores. Forme uses page-native layout where every break is deterministic.
Table headers don’t repeat across pages. HTML <thead> repetition in Chrome’s print mode is inconsistent. In Forme, mark a row as header and it repeats on every page, guaranteed.
Docker images are huge. Chromium adds 400MB+ to your container. Forme has no native dependencies, so your image stays the size of your Node.js base.
Serverless cold starts are painful. Chrome takes 3-10 seconds to start in Lambda or similar environments. Forme’s WASM module loads in under 100ms.
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, positioning, typography, borders, backgrounds). If your templates rely on CSS Grid, CSS animations, or other advanced CSS features, Puppeteer handles all of them.
-
You need complex SVG rendering. Forme supports basic SVG elements (
rect,circle,ellipse,line,polyline,polygon,path), but not advanced SVG features like filters, gradients, masks, or CSS styling within SVG. If your PDFs contain complex charts from D3 or Chart.js, Puppeteer may still handle more of the SVG spec. - 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: