Taking screenshots of websites programmatically is one of those tasks that sounds simple until you actually try it. Here are 5 approaches I've tested, with real code and honest tradeoffs.
1. Puppeteer (The Classic)
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
await browser.close();
Pros: Full control, huge ecosystem, works offline
Cons: 200MB+ memory per instance, cookie banners everywhere, crashes under load
The memory issue is real. I've had production servers OOM after 50 concurrent screenshots. You end up building a queue system, managing browser pools, handling timeouts... it's a whole infrastructure project.
2. Playwright (Puppeteer's Younger Sibling)
const { chromium } = require('playwright');
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'screenshot.png', fullPage: true });
await browser.close();
Pros: Better API, multi-browser support, more reliable waitUntil
Cons: Same memory issues, even larger install size (~400MB with all browsers)
Playwright fixed a lot of Puppeteer's quirks, but the fundamental problem remains: you're running a full browser. For a script that runs once a day, fine. For an API handling 1000 requests/hour, you need serious infrastructure.
3. Screenshot APIs
// Most follow this pattern
const response = await fetch(
'https://api.example.com/screenshot?url=https://example.com&width=1440',
{ headers: { 'Authorization': 'Bearer YOUR_KEY' } }
);
const buffer = await response.arrayBuffer();
fs.writeFileSync('screenshot.png', Buffer.from(buffer));
Pros: No browser to manage, scales instantly, handles edge cases
Cons: Costs money, latency depends on provider, you're trusting a third party
The good ones handle cookie banner removal, lazy-loaded content, and custom viewports. Some (like GrabShot) add device frames and AI cleanup. The bad ones just wrap Puppeteer and charge you $50/month.
When this makes sense: You need screenshots in production and don't want to babysit Chromium processes.
4. html2canvas (Client-Side)
import html2canvas from 'html2canvas';
const canvas = await html2canvas(document.body);
const dataUrl = canvas.toDataURL('image/png');
Pros: No server needed, works in the browser
Cons: Doesn't work with external URLs (CORS), poor CSS support, no iframes
This is for capturing your own page from the client. It's not a general-purpose screenshot tool. If you need to screenshot external sites, skip this one.
5. Sharp + got (For Simple Cases)
// If you just need a thumbnail of an image URL
const sharp = require('sharp');
const got = require('got');
const response = await got('https://example.com/image.png', { responseType: 'buffer' });
const thumbnail = await sharp(response.body)
.resize(400, 300)
.png()
.toBuffer();
Pros: Blazing fast, tiny memory footprint
Cons: Only works with images, not web pages
Obviously this doesn't render HTML. But I've seen people launch Puppeteer just to resize images, so worth mentioning: if your input is already an image, use Sharp.
Performance Comparison
I tested each approach taking 100 screenshots of the same page:
| Method | Avg Time | Memory | Reliability |
|---|---|---|---|
| Puppeteer | 2.8s | 250MB | 94% |
| Playwright | 2.5s | 280MB | 97% |
| Screenshot API | 3.1s | ~0 | 99%+ |
| html2canvas | 1.2s | N/A | 70% |
The API is slower per-request but uses zero local resources and never crashes your server.
My Recommendation
- Prototyping/scripts: Playwright. Better DX than Puppeteer.
- Production API: Use a screenshot service. The infrastructure cost of self-hosting exceeds the API cost surprisingly fast.
- Client-side capture: html2canvas, but temper your expectations.
- Budget is zero: Puppeteer with a browser pool and prayer.
What's your screenshot setup? Curious what others are using in production.
Top comments (0)