Taking Website Screenshots with Node.js Using a Screenshot API
Learn how to capture website screenshots in Node.js using a REST API. Covers fetch, axios, saving to disk, streaming to S3, error handling, and TypeScript types.
Why Use an API Instead of Puppeteer?
The most common approach to taking screenshots in Node.js is launching a headless Chrome instance via Puppeteer or Playwright. That works, but it comes with real costs in production:
- A full Chrome install (~300 MB) bundled into your Lambda or container
- Memory usage of 200–400 MB per browser instance
- Cold start latency on serverless functions
- Maintenance overhead as Chrome updates break your rendering
A screenshot API offloads all of that. Your Node.js code makes a single HTTP request and gets an image back — no browser process to manage.
Prerequisites
- Node.js 18+ (native
fetchavailable) - A WebCaptureAPI key — sign up free, no credit card required
Basic Screenshot with Native fetch
Node.js 18 ships with fetch built in. No extra dependencies needed:
const API_KEY = process.env.WEBCAPTURE_API_KEY
async function screenshot(url) {
const params = new URLSearchParams({ url, format: 'png', width: '1280' })
const response = await fetch(
https://api.webcaptureapi.com/api/screenshot?${params},
{ headers: { 'x-api-key': API_KEY } }
)
if (!response.ok) {
const body = await response.json()
throw new Error(Screenshot failed (${response.status}): ${body.error})
}
return Buffer.from(await response.arrayBuffer())
}
// Usage
const buffer = await screenshot('https://example.com')
await fs.writeFile('screenshot.png', buffer)
Saving to Disk
import fs from 'fs/promises'
async function screenshotToFile(url, outputPath) {
const buffer = await screenshot(url)
await fs.writeFile(outputPath, buffer)
console.log(Saved to ${outputPath})
}
await screenshotToFile('https://example.com', './output.png')
Full-Page Screenshots
To capture the entire scrollable length of a page — not just the visible viewport — pass full_page=true:
async function fullPageScreenshot(url) {
const params = new URLSearchParams({
url,
format: 'png',
width: '1440',
full_page: 'true',
})
const response = await fetch(
https://api.webcaptureapi.com/api/screenshot?${params},
{ headers: { 'x-api-key': process.env.WEBCAPTURE_API_KEY } }
)
if (!response.ok) throw new Error(API error ${response.status})
return Buffer.from(await response.arrayBuffer())
}
Handling JavaScript-Heavy Pages
React, Vue, and Angular apps render asynchronously. If the screenshot fires before the app boots, you'll capture a blank page or loading spinner. Use the delay parameter to wait:
const params = new URLSearchParams({
url: 'https://app.example.com/dashboard',
format: 'jpeg',
quality: '90',
width: '1440',
delay: '2000', // wait 2 seconds after page load
})
Start with 1000–2000ms for most SPAs. Increase if the page still renders incomplete.
Streaming Directly to S3
If you're processing large volumes of screenshots, avoid buffering the entire response in memory. Stream it directly to S3 using the AWS SDK:
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
const s3 = new S3Client({ region: 'us-east-1' })
async function screenshotToS3(url, bucket, key) {
const params = new URLSearchParams({ url, format: 'jpeg', quality: '85' })
const response = await fetch(
https://api.webcaptureapi.com/api/screenshot?${params},
{ headers: { 'x-api-key': process.env.WEBCAPTURE_API_KEY } }
)
if (!response.ok) throw new Error(API error ${response.status})
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: Buffer.from(await response.arrayBuffer()),
ContentType: 'image/jpeg',
}))
return s3://${bucket}/${key}
}
TypeScript Types
interface ScreenshotOptions {
url: string
format?: 'png' | 'jpeg'
width?: number
height?: number
full_page?: boolean
quality?: number // 1–100, JPEG only
delay?: number // ms, max 10000
}
async function screenshot(options: ScreenshotOptions): Promise<Buffer> {
const params = new URLSearchParams(
Object.entries(options)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => [k, String(v)])
)
const response = await fetch(
https://api.webcaptureapi.com/api/screenshot?${params},
{ headers: { 'x-api-key': process.env.WEBCAPTURE_API_KEY! } }
)
if (!response.ok) {
const body = await response.json() as { error: string }
throw new Error(Screenshot failed (${response.status}): ${body.error})
}
return Buffer.from(await response.arrayBuffer())
}
Error Handling
The API returns standard HTTP status codes. Handle them explicitly so your app fails gracefully:
| Status | Meaning | What to do |
|---|---|---|
200 | Success | Read the image buffer |
400 | Bad URL | Check the url param |
401 | Invalid key | Check your WEBCAPTURE_API_KEY env var |
403 | Quota exceeded | Check usage in dashboard or upgrade |
429 | Rate limited | Back off and retry after 60 seconds |
500 | Server error | Retry with exponential backoff |
async function screenshotWithRetry(url, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await fetch(
https://api.webcaptureapi.com/api/screenshot?${new URLSearchParams({ url })},
{ headers: { 'x-api-key': process.env.WEBCAPTURE_API_KEY } }
)
if (response.ok) return Buffer.from(await response.arrayBuffer())
if (response.status === 429 || response.status >= 500) {
if (attempt < maxRetries) {
await new Promise(r => setTimeout(r, 1000 * attempt))
continue
}
}
const body = await response.json()
throw new Error(Screenshot failed (${response.status}): ${body.error})
}
}
Batch Screenshots
To screenshot multiple URLs efficiently, use Promise.allSettled with a concurrency limit:
async function batchScreenshots(urls, concurrency = 5) {
const results = []
for (let i = 0; i < urls.length; i += concurrency) {
const batch = urls.slice(i, i + concurrency)
const settled = await Promise.allSettled(batch.map(url => screenshot({ url })))
results.push(...settled)
}
return results
}
Next Steps
- Python screenshot API guide — same patterns in Python
- Scheduled screenshots — automate recurring captures without writing any polling logic
- API reference — full parameter documentation
Ready to get started?
Create a free account and capture your first screenshot in under 2 minutes.
Get started free