Why developers keep looking for Puppeteer alternatives
Puppeteer was the go-to for HTML screenshots for years. It wraps headless Chromium, it renders real CSS, and it just works — on a machine you control. The problems show up the moment you deploy:
- Binary size. Chromium is ~300 MB. Lambda has a 250 MB limit. Vercel serverless functions have a 250 MB limit. You're already over.
- Cold starts. Launching a new Chromium instance takes 1–3 seconds. Under load, every new container pays that tax.
- Memory. Each Chromium tab uses 200–400 MB. Three concurrent renders and you're at 1 GB.
- Maintenance. Chromium ships updates. Your Linux dependency list shifts. The version you test locally differs from production.
None of these are dealbreakers if you're running a long-lived server you fully control. They all become blockers the moment you try to run image generation on Vercel, Cloudflare Workers, AWS Lambda, or any container with a strict memory ceiling.
The main alternatives
1. Playwright
Playwright is Microsoft's take on the same problem Puppeteer solves. The API is cleaner, multi-browser support is built in, and page.screenshot() works identically. For HTML-to-image use cases the output quality is the same — it's still launching a full browser.
If your problem with Puppeteer is API ergonomics, Playwright solves it. If your problem is binary size, memory, or cold starts, you're trading one headless browser for another. Same root cause.
2. Satori + resvg
Satori (by Vercel) converts JSX to SVG without running a browser. resvg-js then rasterises that SVG to PNG. The output is sharp, the library is tiny, and it deploys anywhere.
The hard constraint: Satori only understands a subset of CSS — flexbox layout, a handful of font properties, no grid, no CSS variables, no pseudo-elements. Any template that relies on real CSS will need to be rewritten from scratch to target Satori's JSX model. The more complex your designs, the bigger that rewrite.
Good fit for: simple, JSX-authored cards with predictable layouts. Bad fit for: existing HTML/CSS templates, anything that uses grid or complex selectors.
3. html-to-image / dom-to-image
These browser-side libraries clone the DOM, inline all styles, and serialize to a canvas or SVG. They run in the user's browser, so there's no server overhead.
The tradeoff: rendering happens on the client, which means quality depends on the user's browser and screen DPI, external fonts may not load in time, cross-origin images break, and you can't use them in a server-side pipeline at all. Fine for a "download this card" button on a web app. Not usable for server-generated OG images.
4. wkhtmltopdf / WeasyPrint
These are HTML-to-PDF converters that can also output images. wkhtmltopdf uses an old WebKit build and has been effectively abandoned since 2020 — it doesn't support modern CSS. WeasyPrint is more actively maintained and handles CSS better, but it's Python-only and its layout engine is not a browser, so edge cases accumulate fast.
Worth considering for: server-side PDF generation where you control the HTML. Not recommended for OG images or anything where pixel-perfect rendering matters.
5. A hosted render API
Services like RenderPix, Bannerbear, and Cloudinary Image Generation accept HTML or a template identifier and return a PNG. You send a POST request; you get an image back. No Chromium binary in your bundle, no cold starts on your side, no memory management.
The tradeoffs are different: you're dependent on an external service, and at high volumes the per-render cost adds up versus running your own infrastructure. But for most products generating thousands of images per month (not millions), the math usually favours an API over maintaining Chromium infrastructure yourself.
Side-by-side comparison
| Tool | Renders real CSS | Serverless-safe | Cold start | Binary size | Maintenance |
|---|---|---|---|---|---|
| Puppeteer | Yes | No | 1–3 s | ~300 MB | High |
| Playwright | Yes | No | 1–3 s | ~300 MB | High |
| Satori + resvg | Subset | Yes | <50 ms | <5 MB | Low |
| html-to-image | Client only | No (browser) | — | — | Low |
| wkhtmltopdf | Partial | Possible | ~500 ms | ~80 MB | High |
| Render API (RenderPix) | Yes | Yes | ~200 ms | 0 MB | None |
Migrating from Puppeteer to a render API
If you're using Puppeteer to take screenshots of HTML templates, the migration to RenderPix is a one-function swap. Here's a typical before/after:
Before — Puppeteer
import puppeteer from 'puppeteer';
export async function renderToImage(html: string): Promise<Buffer> {
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 630 });
await page.setContent(html, { waitUntil: 'networkidle0' });
const screenshot = await page.screenshot({ type: 'png' });
await browser.close();
return screenshot;
}
After — RenderPix
export async function renderToImage(html: string): Promise<Buffer> {
const res = await fetch('https://api.renderpix.dev/v1/render', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.RENDERPIX_API_KEY}`,
},
body: JSON.stringify({ html, width: 1200, height: 630 }),
});
const { url } = await res.json();
const img = await fetch(url);
return Buffer.from(await img.arrayBuffer());
}
Same interface, same output. No Chromium binary, no cold start, deploys cleanly on Vercel or Lambda.
Note on waitUntil: 'networkidle0' — Puppeteer's networkidle0 option waits until the page has made no network requests for 500 ms. RenderPix handles this automatically: the renderer waits for fonts and images to load before capturing. You don't need to configure it.
When to stick with Puppeteer
Puppeteer is still the right tool in some situations:
- You're already running a persistent server that you fully control, memory isn't a constraint, and cold starts don't matter because Chromium stays warm.
- You need browser automation beyond screenshots — form filling, clicking, scraping — and screenshots are a secondary output of that workflow.
- You're generating millions of images per month and the per-render cost of an API would exceed the cost of running dedicated infrastructure.
- Your templates require local resources (file system access, private intranet URLs) that can't be served over the public internet.
Outside those cases, the operational overhead of Puppeteer in a modern serverless-first stack rarely pays for itself.
When to use Satori instead of a render API
Satori makes sense when your designs are simple, you're already writing JSX, and you want zero external dependencies. The cost is a complete rewrite of your templates into Satori's JSX model and ongoing awareness of its CSS limitations.
If you have existing HTML/CSS templates — or if your designers work in HTML — the rewrite cost usually outweighs Satori's simplicity. A render API accepts your HTML directly, so there's nothing to rewrite.
Summary
- Playwright = Puppeteer with a better API. Same deployment constraints.
- Satori = zero-dependency, serverless-safe, but limited to a CSS subset and JSX authoring.
- html-to-image = client-side only, not usable for server-generated images.
- wkhtmltopdf / WeasyPrint = dated or incomplete CSS support, better for PDF than images.
- Render API = full CSS support, no binary, deploys anywhere, zero maintenance. Pay per render.
The right answer depends on your constraints. If you're on serverless, you need full CSS fidelity, and you don't want to own a Chromium fleet — a render API is the shortest path.
Also see: screenshot API for developers.
Replace Puppeteer with a single fetch call
Full HTML and CSS support. Deploys on Vercel, Lambda, Cloudflare Workers — anywhere. Free tier included.