Blog 7 min read

How to Take Full-Page Screenshots in Node.js (2026 Guide)

Capture full-page website screenshots in Node.js using Puppeteer, Playwright, or a screenshot API. Covers scrolling, lazy-loaded content, height limits, and production gotchas.

SnapRender Team
|

How to Take Full-Page Screenshots in Node.js

A full-page screenshot captures the entire scrollable length of a web page, not just the visible viewport. This is useful for archiving, visual regression testing, generating PDFs, and building page preview thumbnails.

Node.js is the most common choice for this because Puppeteer and Playwright both run headless Chromium, which renders pages identically to a real browser. This guide covers three approaches: Puppeteer, Playwright, and a screenshot API.

Method 1: Puppeteer

Puppeteer is Google's official library for controlling headless Chrome.

Setup

npm install puppeteer

Basic Full-Page Capture

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });

await page.screenshot({
  path: 'fullpage.png',
  fullPage: true
});

await browser.close();

The fullPage: true option tells Puppeteer to resize the viewport height to match the full scrollable content, then capture everything in one image.

Handling Lazy-Loaded Content

Many sites load images and content as you scroll. A full-page screenshot will miss this content unless you scroll through the page first:

async function scrollToBottom(page) {
  await page.evaluate(async () => {
    await new Promise((resolve) => {
      let totalHeight = 0;
      const distance = 400;
      const timer = setInterval(() => {
        window.scrollBy(0, distance);
        totalHeight += distance;
        if (totalHeight >= document.body.scrollHeight) {
          clearInterval(timer);
          resolve();
        }
      }, 100);
    });
  });
}

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });

// Scroll through the page to trigger lazy loading
await scrollToBottom(page);

// Scroll back to top and wait for images to finish loading
await page.evaluate(() => window.scrollTo(0, 0));
await new Promise(resolve => setTimeout(resolve, 2000));

await page.screenshot({ path: 'fullpage.png', fullPage: true });
await browser.close();

This adds 2-5 seconds to each capture depending on page length. There is no way around it if the site uses lazy loading.

Output Formats

// PNG (lossless, larger files)
await page.screenshot({ path: 'page.png', fullPage: true, type: 'png' });

// JPEG (lossy, smaller files)
await page.screenshot({ path: 'page.jpg', fullPage: true, type: 'jpeg', quality: 85 });

// WebP (best compression)
await page.screenshot({ path: 'page.webp', fullPage: true, type: 'webp', quality: 80 });

Full-page PNGs of content-heavy sites can easily exceed 5-10MB. JPEG or WebP at quality 80-85 typically cuts that by 60-80%.

Retina / High-DPI Capture

await page.setViewport({
  width: 1280,
  height: 800,
  deviceScaleFactor: 2
});

await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'retina.png', fullPage: true });

This produces a 2560px-wide image with sharper text and graphics. The file size roughly quadruples compared to 1x.

Method 2: Playwright

Playwright is Microsoft's alternative to Puppeteer. The API is similar but supports Chromium, Firefox, and WebKit.

Setup

npm install playwright

Full-Page Capture

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });

await page.goto('https://example.com', { waitUntil: 'networkidle' });

await page.screenshot({
  path: 'fullpage.png',
  fullPage: true
});

await browser.close();

Playwright uses waitUntil: 'networkidle' (no 2 suffix) and handles some lazy-loading scenarios better than Puppeteer out of the box, but you still need manual scrolling for many sites.

Playwright vs Puppeteer for Screenshots

Both produce identical output when using Chromium. The differences that matter for screenshots:

  • Playwright auto-scrolls to trigger some lazy-loaded content before full-page capture. Puppeteer does not.
  • Playwright supports Firefox and WebKit rendering (useful if you need cross-browser screenshots).
  • Puppeteer has a slightly smaller install size and is more widely used in the screenshot API space.

For most full-page screenshot use cases, either works fine.

The Hard Parts

Both Puppeteer and Playwright make the basic case easy. The problems show up in production:

Height Limits

Chromium has a maximum texture size. On most systems, a single screenshot cannot exceed 16,384px or 32,768px in height depending on the GPU. Pages longer than this will be clipped silently or crash the renderer.

// Check page height before capturing
const bodyHeight = await page.evaluate(() => document.body.scrollHeight);

if (bodyHeight > 32000) {
  console.warn(`Page is ${bodyHeight}px tall, will be clipped at 32768px`);
}

await page.screenshot({ path: 'fullpage.png', fullPage: true });

There is no clean workaround. You can capture the page in tiles and stitch them together, but that adds significant complexity.

Infinite Scroll Pages

Sites like Twitter, Reddit, and Pinterest load content forever as you scroll. A full-page screenshot of these sites will either hang indefinitely or produce an enormous image.

// Set a maximum scroll limit for infinite scroll sites
async function scrollWithLimit(page, maxHeight = 15000) {
  await page.evaluate(async (limit) => {
    await new Promise((resolve) => {
      let totalHeight = 0;
      const distance = 400;
      const timer = setInterval(() => {
        window.scrollBy(0, distance);
        totalHeight += distance;
        if (totalHeight >= limit || totalHeight >= document.body.scrollHeight) {
          clearInterval(timer);
          resolve();
        }
      }, 100);
    });
  }, maxHeight);
}

Memory Usage

Each Chromium tab uses 50-300MB of RAM. Full-page screenshots of long pages can spike to 500MB+ because the renderer must hold the entire page bitmap in memory. Running multiple concurrent captures requires careful memory management:

// Limit concurrent captures
const MAX_CONCURRENT = 5;
let active = 0;

async function captureWithLimit(url) {
  while (active >= MAX_CONCURRENT) {
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  active++;
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1280, height: 800 });
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
    const buffer = await page.screenshot({ fullPage: true });
    await page.close();
    return buffer;
  } finally {
    active--;
  }
}

Sticky Headers and Footers

Fixed-position elements (sticky navbars, cookie banners, floating CTAs) repeat in every viewport-sized chunk of a full-page screenshot. This looks broken in the final image.

// Remove sticky elements before capturing
await page.evaluate(() => {
  const elements = document.querySelectorAll('*');
  for (const el of elements) {
    const style = getComputedStyle(el);
    if (style.position === 'fixed' || style.position === 'sticky') {
      el.style.position = 'absolute';
    }
  }
});

await page.screenshot({ path: 'clean.png', fullPage: true });

This fixes the duplication but may break the page layout in some cases.

Cookie Banners

GDPR cookie banners cover large portions of the page. Removing them reliably requires maintaining a list of selectors for every major consent framework (OneTrust, CookieBot, Quantcast, custom implementations):

const cookieSelectors = [
  '#onetrust-banner-sdk',
  '#CybotCookiebotDialog',
  '.cookie-banner',
  '[class*="cookie-consent"]',
  '[id*="cookie-notice"]',
];

for (const selector of cookieSelectors) {
  await page.evaluate((sel) => {
    document.querySelectorAll(sel).forEach(el => el.remove());
  }, selector);
}

This list needs constant maintenance as sites change their implementations.

Method 3: Use a Screenshot API

If you are capturing screenshots as part of a product or service (not just a one-off script), a screenshot API handles all the edge cases above: lazy loading, height limits, cookie banners, ad blocking, memory management, and crash recovery.

Here is the same full-page screenshot using SnapRender:

cURL:

curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=png&fullPage=true&blockCookieBanners=true&blockAds=true" \
  -H "X-API-Key: YOUR_API_KEY" \
  --output fullpage.png

Node.js SDK:

npm install snaprender
import { SnapRender } from 'snaprender';

const client = new SnapRender('YOUR_API_KEY');

const buffer = await client.screenshot({
  url: 'https://example.com',
  fullPage: true,
  blockCookieBanners: true,
  blockAds: true,
  format: 'png'
});

Python SDK:

pip install snaprender
from snaprender import SnapRender

client = SnapRender("YOUR_API_KEY")

image = client.screenshot(
    url="https://example.com",
    full_page=True,
    block_cookie_banners=True,
    block_ads=True,
    format="png"
)

One request replaces the scroll-to-load logic, cookie banner removal, ad blocking, height capping, memory management, and error handling that you would otherwise build yourself.

SnapRender offers 500 free screenshots per month with no credit card required. Sign up and get an API key in 30 seconds.

Which Approach Should You Use?

Scenario Recommended
Local testing / CI visual regression Puppeteer or Playwright
One-off script for personal use Puppeteer
Cross-browser screenshot comparison Playwright
Production service capturing user-submitted URLs Screenshot API
Batch capturing hundreds of pages Screenshot API
Mobile + desktop + dark mode variants Screenshot API

For anything that runs in production and handles arbitrary URLs, a managed API saves you from maintaining browser infrastructure, handling crashes, and fighting with lazy loading and cookie banners.

Try SnapRender Free

500 free screenshots/month, no credit card required.

Sign up free