Puppeteer works. For simple scripts and one-off tasks, it's fine. But teams running Puppeteer in production at any kind of scale have a recurring set of problems — and they're looking for something better.
This post covers the most practical alternatives, with code showing how to migrate.
Why Teams Replace Puppeteer
The pain points developers report
- Memory leaks — browser instances balloon to 500MB+ and never release
- Chromium crashes under concurrent load, taking the whole process with it
- 300MB+ binary that breaks Docker layer caching and Vercel/Lambda limits
- Cold starts of 3-8 seconds on serverless (Chromium boot time)
- puppeteer-extra-plugin-stealth not maintained, anti-bot detection improving
- No built-in queue — you build your own, then debug it forever
- Async errors swallowed silently — debugging browser crashes is painful
The short version: Puppeteer is great for a script. It becomes a maintenance burden when it's a production dependency handling real user traffic.
Alternative 1: Playwright
Microsoft's successor to Puppeteer. Better API, actively maintained, multi-browser, multi-language. If you need a self-hosted browser automation library, Playwright is the answer in 2026.
Key improvements over Puppeteer
- Auto-waiting — Playwright waits for elements to be actionable before interacting. Puppeteer requires manual waits.
- Browser contexts — isolate cookies and sessions per context, not per browser instance.
- Multi-language — Node.js, Python, .NET, Java. Puppeteer is Node.js only.
- Tracing and test reporter — built-in debugging tools.
- Maintained by Microsoft — Puppeteer is Google, but Playwright has had more active development.
Migrating screenshots from Puppeteer
// Puppeteer (before)
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const screenshot = await page.screenshot({ fullPage: true });
await browser.close();
// Playwright (after) — nearly identical API
const { chromium } = require('playwright');
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
const screenshot = await page.screenshot({ fullPage: true });
await browser.close();
Migrating PDF generation
// Puppeteer PDF
const pdf = await page.pdf({ format: 'A4', printBackground: true });
// Playwright PDF — identical
const pdf = await page.pdf({ format: 'A4', printBackground: true });
For most use cases, migrating from Puppeteer to Playwright is a find-and-replace. The API is nearly identical. The differences show up in error handling, auto-waiting behavior, and browser context management — all improvements.
The catch: Playwright has the same underlying infrastructure problems as Puppeteer. Memory-per-instance (~150-200MB), crash risk under concurrent load, serverless cold start penalty. Playwright is a better library — it doesn't solve the operational challenges of running headless Chromium in production.
Alternative 2: Screenshot/PDF API
Replace Puppeteer with an HTTP call. Their infrastructure handles Chromium pooling, concurrency, memory management, and crash recovery. You get a URL or bytes back.
Screenshot: Puppeteer → SnapAPI
// Before: Puppeteer
import puppeteer from 'puppeteer';
async function takeScreenshot(url) {
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
await page.goto(url, { waitUntil: 'networkidle2' });
const buffer = await page.screenshot({ type: 'png', fullPage: true });
await browser.close();
return buffer; // ~300MB browser used, then discarded
}
// After: SnapAPI
async function takeScreenshot(url) {
const res = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': 'sk_live_xxx', 'Content-Type': 'application/json' },
body: JSON.stringify({
url,
format: 'png',
full_page: true,
viewport_width: 1440,
viewport_height: 900,
})
});
const { url: screenshotUrl } = await res.json();
return screenshotUrl; // CDN URL, valid 24h
}
PDF: Puppeteer → SnapAPI
// Before: Puppeteer PDF
async function generatePDF(url) {
const browser = await puppeteer.launch({ headless: 'new' });
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle0' });
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' }
});
await browser.close();
return pdf;
}
// After: SnapAPI
async function generatePDF(url) {
const res = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': 'sk_live_xxx', 'Content-Type': 'application/json' },
body: JSON.stringify({
url,
format: 'pdf',
pdf_format: 'A4',
pdf_print_background: true,
pdf_margin_top: '20mm',
pdf_margin_bottom: '20mm',
pdf_margin_left: '15mm',
pdf_margin_right: '15mm',
})
});
const { url: pdfUrl } = await res.json();
return pdfUrl;
}
What you gain by dropping Puppeteer
- Zero Chromium binary — your Docker image drops ~300MB immediately
- Serverless-compatible — one HTTP call works anywhere, including Lambda, Vercel Edge, Cloudflare Workers
- No memory management — concurrency, pooling, crash recovery all handled externally
- Scraping + extraction + video included — not just screenshots
- MCP server — AI agents (Claude Code, Cursor) can call your screenshot capability directly
What you trade off
- Network latency (~200-600ms) — not suitable for millisecond-sensitive paths
- URL must be publicly accessible (or you use signed tokens to secure it)
- Not free at very high volume (50K req/mo is $79)
Alternative 3: Browserless
A managed Chromium-as-a-service layer. You connect your existing Puppeteer/Playwright code to their remote browser fleet over WebSocket. Less migration work, same browser quality.
// Connect existing Puppeteer to Browserless
import puppeteer from 'puppeteer';
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://chrome.browserless.io?token=YOUR_TOKEN',
});
const page = await browser.newPage();
// rest of your code stays the same
Browserless is a good middle-ground if you have extensive Puppeteer code you don't want to rewrite. You keep your Puppeteer API while outsourcing the browser fleet management. The trade-off: you still pay per browser minute, and the API flexibility is limited compared to a purpose-built capture API.
Comparison Table
| Option | Binary size | Memory/req | Serverless | Migration effort | PDF + scraping |
|---|---|---|---|---|---|
| Puppeteer | ~300MB | 150-200MB | Painful | — | Manual |
| Playwright | ~600MB | 150-200MB | Painful | Low | Manual |
| Browserless | Zero | Zero (remote) | Yes | Very low | Limited |
| SnapAPI | Zero | Zero | Yes | Low | Built-in |
Migration Guide: Puppeteer → SnapAPI in 30 Minutes
Here's a practical checklist for replacing Puppeteer with SnapAPI in an existing project:
- Get an API key — snapapi.pics/register.html, 200 free req/month
- Install the SDK —
npm install snapapi-js(or use plainfetch) - Find all
puppeteer.launch()calls — these are your migration points - Replace screenshot calls — swap
page.screenshot()withPOST /v1/screenshot - Replace PDF calls — swap
page.pdf()withPOST /v1/screenshot+format: 'pdf' - Replace scraping calls — swap
page.evaluate()withPOST /v1/scrape - Remove Puppeteer from dependencies —
npm uninstall puppeteer puppeteer-extra puppeteer-extra-plugin-stealth - Rebuild Docker image — enjoy the 300MB+ reduction
Which to Pick
You need browser automation (clicking, form submission, interaction): Playwright. There's no API-based alternative — you need a real browser for this.
You need screenshots and PDFs at scale: SnapAPI. Replace Puppeteer with HTTP calls, eliminate Chromium from your stack, and stop dealing with browser crashes and memory leaks.
You have lots of Puppeteer code and want minimal rewrite: Browserless. Connect your existing Puppeteer code to a managed fleet. Good middle path.
You're on serverless (Lambda, Vercel, Fly.io): SnapAPI or Browserless. Running Chromium in Lambda is painful — 250MB+ layer, cold starts, memory limits. An HTTP call is infinitely simpler.
Drop Puppeteer. One HTTP call, zero Chromium.
200 free screenshots/month, no credit card. Works from any Node.js, Python, or Go app.
Get your free API key →