ScreenshotNeo

BlogHow-to

How to Handle Large Images in Puppeteer

Capture pages with large images reliably in Puppeteer: choose the right screenshot scope, wait for visual readiness, and avoid unnecessary work.

By the ScreenshotNeo team4 October 20269 min read

To handle large images in Puppeteer, set the viewport before navigation, wait for the page and the specific images you need to become visually ready, then capture only the required scope. Use page.screenshot() for the viewport or full page, element.screenshot() for one image or component, and clip for a region. Network idle is a useful timing signal, but it does not prove that every image has finished decoding.

This guide uses Puppeteer’s documented screenshot APIs. There is no documented universal maximum image size or memory budget in the cited Puppeteer references; behavior depends on the page, browser, image dimensions, and capture extent.

1. Install Puppeteer and make a basic capture

Install Puppeteer in a Node.js project. The package downloads a compatible browser by default; if you use a separately managed browser, configure its executable path as appropriate for your environment.

npm install puppeteer

Save this as capture.mjs and run it with node capture.mjs. Set the viewport before navigating so responsive pages lay out at the dimensions you intend to capture.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
  await page.goto('https://example.com', {
    waitUntil: 'networkidle2',
    timeout: 60_000,
  });
  await page.screenshot({ path: 'page.png', type: 'png' });
} finally {
  await browser.close();
}

The Puppeteer [screenshots guide](https://pptr.dev/guides/screenshots) demonstrates navigation with networkidle2. Treat it as a lifecycle wait, not a guarantee of image readiness. For mobile layout, set the intended viewport before navigation; Puppeteer notes that changing mobile or touch settings can trigger a reload on some pages ([viewport API](https://pptr.dev/api/puppeteer.page.setviewport)).

2. Wait for the large images you actually need

A page can become network-idle while images are still pending, deferred until they approach the viewport, or not yet decoded for display. For a known target image, wait for it to load and decode before capturing. The following helper checks the image element’s load state and calls the browser’s decode() method; it also handles an image that already completed before the check ran.

async function waitForImage(page, selector, timeout = 30_000) {
  await page.waitForSelector(selector, { timeout });
  return page.$eval(selector, async (img) => {
    if (!(img instanceof HTMLImageElement)) {
      throw new Error('The selector did not match an image element');
    }
    if (!img.complete) {
      await new Promise((resolve, reject) => {
        img.addEventListener('load', resolve, { once: true });
        img.addEventListener('error', () => reject(new Error(`Image failed: ${img.currentSrc || img.src}`)), { once: true });
      });
    }
    if (img.naturalWidth === 0) {
      throw new Error(`Image has no decoded dimensions: ${img.currentSrc || img.src}`);
    }
    await img.decode();
    return { src: img.currentSrc || img.src, width: img.naturalWidth, height: img.naturalHeight };
  });
}

import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
  await page.goto('https://example.com/gallery', { waitUntil: 'domcontentloaded', timeout: 60_000 });
  const image = await waitForImage(page, 'img.hero');
  console.log('Ready image:', image);
  await page.screenshot({ path: 'hero-page.png', type: 'png' });
} finally {
  await browser.close();
}

The helper is application-specific logic, not a Puppeteer guarantee that every image on every page is ready. Adjust it for your markup: a site may put the real URL in a data attribute, use a CSS background image, swap sources with JavaScript, or expose a loading indicator. The browser image APIs provide the checks shown here; see MDN’s [HTMLImageElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement) and [decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) references.

Lazy-loaded images below the fold

Lazy-loaded images may not start loading until they are near the viewport. If a full-page capture needs them, scroll through the document in increments, allow each area to load, and then verify the target images before taking the screenshot. Avoid assuming that scrolling alone loaded every image.

async function loadLazyImages(page, step = 700, pauseMs = 200) {
  await page.evaluate(async ({ step, pauseMs }) => {
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    const height = () => Math.max(
      document.body.scrollHeight,
      document.documentElement.scrollHeight
    );
    for (let y = 0; y < height(); y += step) {
      window.scrollTo(0, y);
      await sleep(pauseMs);
    }
    window.scrollTo(0, 0);
  }, { step, pauseMs });
  await page.waitForNetworkIdle({ concurrency: 0, idleTime: 500, timeout: 30_000 }).catch(() => {});
}

// After page.goto(...), before a full-page capture:
await loadLazyImages(page);
const broken = await page.$$eval('img', (imgs) =>
  imgs.filter((img) => !img.complete || img.naturalWidth === 0)
    .map((img) => img.currentSrc || img.src)
);
if (broken.length) console.warn('Images not ready:', broken);
await page.screenshot({ path: 'full.png', fullPage: true });

The scroll pause is a tunable delay, not a readiness guarantee. For production captures, prefer checking the specific required images or the site’s own loading signal. This example uses Puppeteer’s network-idle wait, whose documented default idle period is 500 ms; the API describes network quiet, not image decoding ([waitForNetworkIdle](https://pptr.dev/api/puppeteer.page.waitfornetworkidle), [options](https://pptr.dev/api/puppeteer.waitfornetworkidleoptions)).

3. Choose the smallest screenshot scope that answers the task

Need Method When to use it
Visible viewport page.screenshot() Default for previews and above-the-fold checks.
One image or component element.screenshot() Use a selector for the image, chart, or card. Puppeteer attempts to scroll a hidden element into view.
Specific rectangular area clip Capture a known region without the rest of the page.
Whole document fullPage: true Use only when complete document coverage is required.

Capture just an image element

const imageElement = await page.waitForSelector('img.hero', { timeout: 30_000 });
if (!imageElement) throw new Error('Hero image was not found');
await waitForImage(page, 'img.hero');
await imageElement.screenshot({ path: 'hero.webp', type: 'webp', quality: 85 });

Capture a clipped region

await page.screenshot({
  path: 'region.png',
  clip: { x: 100, y: 120, width: 900, height: 600 },
});

Clip coordinates are page screenshot coordinates. Ensure the selected rectangle covers the intended area and remains within the rendered page. For capture beyond the viewport, the screenshot options include captureBeyondViewport; exact behavior can depend on the selected capture mode. See Puppeteer’s [ScreenshotOptions](https://pptr.dev/api/puppeteer.screenshotoptions) and [screenshots guide](https://pptr.dev/guides/screenshots).

Capture the complete page

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

A very tall page can produce a large output and require more browser work than a viewport or element capture. Puppeteer’s documentation does not specify a universal height cap or memory allowance. If a full-page capture fails or is too costly for your job, capture a particular element or split the document into intentional regions.

4. Format, dimensions, and screenshot options

  • Type: PNG is the default. JPEG and WebP can be selected where supported by your Puppeteer/browser setup.
  • Quality: Applies to non-PNG formats; it does not reduce PNG output quality or size through the quality option.
  • Viewport: Set width and height before navigation when responsive layout matters. deviceScaleFactor controls the device pixel ratio and can increase pixel dimensions.
  • Extent: Choose viewport, fullPage, clip, or an element screenshot deliberately.
  • Encoding: Screenshot APIs can return bytes or base64 depending on the encoding option; use a file path for direct output to disk.
  • Background: Screenshot options include background handling such as omitting the default background where supported.

Use PNG when you need lossless output or transparency. For photographic content, JPEG or WebP with an appropriate quality can reduce output size, but compare the resulting image against your use case. The official option list and constraints are in [ScreenshotOptions](https://pptr.dev/api/puppeteer.screenshotoptions) and [Page.screenshot()](https://pptr.dev/api/puppeteer.page.screenshot).

5. Reliability, performance, and cost

  • Wait for a condition tied to the visual target. Network idle can be delayed by polling or long-lived requests, and can occur before an image is decoded. Use a selector, image decode check, or application loading state for the required content.
  • Keep capture scope narrow. A target element or clip avoids capturing unrelated page area when that is all you need. Full-page capture should be reserved for full-page requirements.
  • Control pixel count. Large viewport dimensions and a higher device scale factor increase screenshot pixels and can increase processing and output size. Choose dimensions based on the consuming system.
  • Be deliberate about format. PNG is lossless; non-PNG quality options may trade detail for smaller files. Quality does not apply to PNG.
  • Close the browser reliably. Use try/finally so browser processes are closed on navigation or capture errors. Puppeteer’s getting-started workflow also closes the browser ([guide](https://pptr.dev/guides/getting-started)).
  • Budget for page behavior. Image servers, redirects, consent overlays, and browser resource limits can affect completion time. The cited Puppeteer references provide no quantified memory benchmark or guaranteed performance for large images; measure with your own pages and deployment limits.

Puppeteer itself does not bill per screenshot. Your operational costs come from the compute, browser runtime, storage, transfer, and any services you use around it. Keep concurrency within the memory and CPU capacity of your workers, and avoid retrying immediately when the underlying image URL is invalid or consistently blocked.

6. Troubleshooting large-image captures

Symptom Likely cause Fix
Image area is blank Lazy loading has not started, the image failed, or capture ran before decode. Scroll the target into view, wait for its selector and load state, verify naturalWidth, and await decode().
Some lower-page images are missing Full-page capture did not trigger the page’s lazy-loading behavior. Scroll through the page to trigger loading, then verify required images before capturing.
waitForNetworkIdle times out Analytics, polling, or other requests keep the network active. Use a targeted readiness condition for the content you need; do not treat network idle as mandatory proof of image readiness.
Screenshot is much larger than expected Full-page extent, large viewport, or high device scale factor produced many pixels. Capture an element or clip, reduce the viewport or scale factor, or choose a non-PNG format and suitable quality.
Target selector is not found The page has not rendered the component, selector differs, or content is inside a frame/shadow root. Confirm the selector in the page, wait for the correct render signal, and query the relevant frame or shadow-root content with page-specific logic.
Image loads in a normal browser but not automation The server may require authentication, cookies, headers, or a user-agent behavior. Reproduce the required authorized session and inspect page/network errors. Do not bypass access controls.
Browser crashes on full-page capture Capture dimensions or concurrent jobs exceed available worker resources. Reduce capture scope or scale, lower concurrency, and split very tall pages into regions.
quality appears to have no effect PNG was selected or is the default. Choose a supported non-PNG type such as JPEG or WebP; the quality option is for non-PNG output.

7. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It can return PNG, JPEG, WebP, or PDF from one GET request. Its screenshot options include full-page capture with lazy images loaded, element selection, viewport and device presets, custom waits, and image resizing. See the API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

Cookie banners are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers report the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

8. FAQ

Does Puppeteer set a maximum size for an image?

The cited Puppeteer documentation does not specify a universal maximum image size. Your browser and worker resources, the page, and the screenshot dimensions determine practical limits.

Does networkidle2 mean every image is ready?

No. It indicates a network lifecycle condition. Verify the particular image’s load and decode state when visual readiness matters.

Should I use full-page capture for one large image?

Usually not. Capture the image element directly when the image itself is the output you need.

Can I lower PNG quality with Puppeteer’s quality option?

No. Puppeteer documents that quality applies to formats other than PNG. Choose JPEG or WebP if a lossy quality setting is appropriate.