Menu
nodejstutorialscreenshots

How to Take Website Screenshots in Node.js (3 Methods)

SnapSharp Team·March 26, 2026·4 min read

Node.js is the most popular runtime for server-side screenshot capture. This guide covers three approaches — Playwright, Puppeteer, and the SnapSharp API — with production-ready code for each.

Method 1: Playwright

Playwright is the modern standard for browser automation. It supports Chromium, Firefox, and WebKit from a single API.

Installation

npm install playwright
npx playwright install chromium

Basic Screenshot

import { chromium } from "playwright";

async function screenshot(url: string, output: string = "screenshot.png") {
  const browser = await chromium.launch();
  const page = await browser.newPage({
    viewport: { width: 1280, height: 720 },
  });
  await page.goto(url, { waitUntil: "networkidle" });
  await page.screenshot({ path: output });
  await browser.close();
}

await screenshot("https://example.com");

Full-Page Screenshot

await page.screenshot({ path: "full.png", fullPage: true });

Dark Mode

const page = await browser.newPage({
  viewport: { width: 1280, height: 720 },
  colorScheme: "dark",
});

Element Screenshot

const hero = page.locator(".hero-section");
await hero.screenshot({ path: "hero.png" });

PDF Export

await page.pdf({
  path: "page.pdf",
  format: "A4",
  printBackground: true,
});

Pros and Cons

Pros: Modern API, great TypeScript support, cross-browser, excellent auto-wait, active development by Microsoft.

Cons: ~150 MB browser download, ~200 MB RAM per instance, you handle concurrency, Chrome updates can break things.


Method 2: Puppeteer

Puppeteer is Google's Chrome automation library. It's been around since 2017 and has the largest ecosystem of plugins and tutorials.

Installation

npm install puppeteer

Puppeteer downloads Chromium automatically during install.

Basic Screenshot

import puppeteer from "puppeteer";

async function screenshot(url: string, output: string = "screenshot.png") {
  const browser = await puppeteer.launch({
    headless: true,
    args: ["--no-sandbox", "--disable-setuid-sandbox"],
  });
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 720 });
  await page.goto(url, { waitUntil: "networkidle2" });
  await page.screenshot({ path: output });
  await browser.close();
}

await screenshot("https://example.com");

Full-Page Screenshot

await page.screenshot({ path: "full.png", fullPage: true });

Block Requests (Ad Blocking)

await page.setRequestInterception(true);
page.on("request", (req) => {
  const blocked = ["ads", "analytics", "tracking"];
  if (blocked.some((b) => req.url().includes(b))) {
    req.abort();
  } else {
    req.continue();
  }
});

Wait for Specific Element

await page.goto(url);
await page.waitForSelector(".content-loaded", { timeout: 10_000 });
await page.screenshot({ path: output });

Pros and Cons

Pros: Huge ecosystem, Google-maintained, well-documented, works in Docker easily.

Cons: Chrome-only (no Firefox/WebKit), older API design, lacks Playwright's auto-wait, slightly higher memory usage.


Method 3: SnapSharp API

The SnapSharp API renders pages in the cloud and returns images over HTTP. No local browser needed — works in serverless functions, edge runtimes, and CI/CD.

Installation

npm install snapsharp

Basic Screenshot (SDK)

import { SnapSharp } from "snapsharp";

const snap = new SnapSharp(process.env.SNAPSHARP_API_KEY!);

const image = await snap.screenshot("https://example.com", {
  width: 1280,
  height: 720,
});
await Bun.write("screenshot.png", image);
// Or in Node: fs.writeFileSync("screenshot.png", Buffer.from(image));

Basic Screenshot (fetch)

const response = await fetch(
  "https://api.snapsharp.dev/v1/screenshot?" +
    new URLSearchParams({
      url: "https://example.com",
      width: "1280",
      height: "720",
    }),
  {
    headers: { Authorization: `Bearer ${process.env.SNAPSHARP_API_KEY}` },
  }
);

const buffer = await response.arrayBuffer();

Full-Page + Dark Mode + WebP

const image = await snap.screenshot("https://example.com", {
  fullPage: true,
  darkMode: true,
  format: "webp",
  blockAds: true,
});

Use in Next.js API Route

// app/api/screenshot/route.ts
import { SnapSharp } from "snapsharp";

const snap = new SnapSharp(process.env.SNAPSHARP_API_KEY!);

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const url = searchParams.get("url");
  if (!url) return Response.json({ error: "url required" }, { status: 400 });

  const image = await snap.screenshot(url, {
    width: 1200,
    height: 630,
    format: "png",
  });

  return new Response(image, {
    headers: {
      "Content-Type": "image/png",
      "Cache-Control": "public, max-age=86400",
    },
  });
}

Use in Express

import express from "express";
import { SnapSharp } from "snapsharp";

const app = express();
const snap = new SnapSharp(process.env.SNAPSHARP_API_KEY!);

app.get("/screenshot", async (req, res) => {
  const url = req.query.url as string;
  if (!url) return res.status(400).json({ error: "url required" });

  const image = await snap.screenshot(url, { width: 1280 });
  res.set("Content-Type", "image/png");
  res.send(Buffer.from(image));
});

app.listen(3000);

Batch Capture

const urls = [
  "https://example.com",
  "https://github.com",
  "https://vercel.com",
];

const results = await Promise.all(
  urls.map(async (url) => {
    const image = await snap.screenshot(url, { width: 1280 });
    const domain = new URL(url).hostname;
    const path = `${domain}.png`;
    await Bun.write(path, image);
    return path;
  })
);

Pros and Cons

Pros: Zero local dependencies, works in serverless/edge, elastic scaling, ad blocking and dark mode built-in, handles Chrome maintenance.

Cons: Network latency (~2–4s), costs at high volume (100 free/month), requires internet access.


Comparison Table

FeaturePlaywrightPuppeteerSnapSharp API
Install size~150 MB (browser)~150 MB (browser)~50 KB (SDK)
Local browserRequiredRequiredNot needed
Full-pageBuilt-inBuilt-inBuilt-in
Dark modecolorSchemeManual emulationdarkMode: true
Element captureBuilt-inManual clipselector param
PDF exportBuilt-inBuilt-inBuilt-in
Ad blockingRoute interceptionRequest interceptionblockAds: true
Cross-browserChromium, Firefox, WebKitChromium onlyChromium (managed)
Serverless-readyNo (binary too large)No (binary too large)Yes
RAM per capture~200 MB~250 MB0 (cloud)
Best forE2E testing, local toolsChrome-specific automationAPIs, serverless, SaaS

Which Should You Choose?

  • Playwright if you need cross-browser testing or local automation with the best modern API.
  • Puppeteer if you have existing Puppeteer code or need Chrome DevTools Protocol access.
  • SnapSharp API if you're building a production service and don't want to manage browser infrastructure.

All three produce identical screenshot quality for Chromium. The decision is about where the browser runs and who maintains it.

Get a free SnapSharp API key →

Frequently Asked Questions

How do I take a full-page screenshot in Node.js?

With Playwright and Puppeteer, pass fullPage: true to page.screenshot(). With the SnapSharp API, add full_page=true to the query string (or fullPage: true via the SDK). The API also supports full_page_max_height to cap pathologically long pages.

Can Playwright or Puppeteer run in AWS Lambda or Vercel?

Not reliably. Chromium binaries are ~150 MB, which exceeds Lambda's 50 MB deployment limit unless you use chrome-aws-lambda and careful layering. Cold starts are 3–5 seconds. For serverless, an API-based approach like SnapSharp avoids the binary entirely — you only ship a ~50 KB SDK.

How do I screenshot a single element instead of the whole page?

In Playwright: await page.locator('.hero').screenshot(...). In Puppeteer: use elementHandle.screenshot(). With SnapSharp, pass selector=.hero — the API waits for the element and captures just its bounding box.

Why does my Node.js screenshot job crash after a few hundred requests?

Chromium leaks memory over time and browser.close() doesn't always fully release resources. In production you need a browser pool with periodic recycling (e.g. restart every 100 requests). SnapSharp handles this internally via its browser pool service — see Why Headless Chrome Keeps Crashing.

Which format should I use — PNG, JPEG, or WebP?

PNG for UI screenshots with text and sharp edges. JPEG with quality: 80–85 for photographic content (30–50% smaller). WebP for modern browsers — smallest file size with good quality. The SnapSharp API accepts any of the three via the format parameter.

Can I generate PDFs instead of images?

Yes. Playwright and Puppeteer both have page.pdf() with options for format, margins, and headers. SnapSharp exposes the same via POST /v1/pdf (Growth plan and above) — see the PDF endpoint docs.


Related: Screenshot API docs · Pricing · Automate Screenshots with Python

How to Take Website Screenshots in Node.js (3 Methods) — SnapSharp Blog