We use cookies

We use cookies to enhance your experience and analyse site usage.

Back to Blog
Tutorial7 min read

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 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

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:

StatusMeaningWhat to do
200SuccessRead the image buffer
400Bad URLCheck the url param
401Invalid keyCheck your WEBCAPTURE_API_KEY env var
403Quota exceededCheck usage in dashboard or upgrade
429Rate limitedBack off and retry after 60 seconds
500Server errorRetry 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

Ready to get started?

Create a free account and capture your first screenshot in under 2 minutes.

Get started free