Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To keep Puppeteer’s memory use under control, limit how many pages are active at once, close each page when its work is done, and block only the network resources your task can safely omit. Measure the browser’s real memory use with your own URLs before choosing a concurrency or restart threshold: Puppeteer does not document a universal RAM-per-page figure or a browser memory cap.

Why Puppeteer memory use varies

A page is not a fixed-size unit of memory. Usage changes with the website, the amount of JavaScript running, loaded media, the number of pages and contexts, and the browser version and workload. Several simultaneous pages also mean more active renderer work. As a result, a number measured on one URL or machine is not a reliable capacity promise for another.

It helps to distinguish browser memory from Node.js memory. A Node heap limit such as --max-old-space-size applies to Node’s JavaScript heap; it does not set a ceiling on Chromium’s renderer processes. If the browser is the part consuming memory, raising or lowering the Node heap limit is not a direct fix.

Likewise, settings for Puppeteer’s cache directory, temporary directory, or executable path address where browser files are stored and which browser is launched. Puppeteer downloads Chrome for Testing and chrome-headless-shell by default, but those installation and disk-storage details do not establish a runtime RAM limit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to reduce memory in a Puppeteer job

1. Put a deliberate limit on concurrent pages

Use a queue or worker pool instead of opening a page for every URL at once. Start with a conservative worker count, then measure throughput and memory on a representative mix of pages. Increasing parallelism may improve completion time, but it also increases simultaneous renderer work and the minimum memory needed while the job runs. There is no universal correct number of pages per browser.

Concurrency limits also apply outside the scraper. Puppeteer’s troubleshooting guide gives jest --maxWorkers=2 as an example of limiting workers in tests; it is an example, not a recommended setting for every machine or workload.

2. Close pages and contexts deterministically

When a task finishes, call await page.close() in a finally block so errors do not leave pages open. If you created a browser context for a group of tasks, close or dispose of that context once it is no longer needed. Avoid retaining page objects in result arrays, event listeners, closures, or other long-lived objects after the work is complete.

Closing a page makes its lifecycle explicit, but a long-running process should still be measured rather than assumed to return to a particular RSS value. Browser and application state can persist while the browser remains alive.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

3. Intercept and abort resources the task does not need

If extraction or rendering does not depend on a resource, request interception can avoid downloading it. Images, fonts, video, advertising, and analytics may be candidates. Keep stylesheets and scripts when the page’s layout, client-side rendering, authentication, or extraction logic relies on them.

Once interception is enabled, every intercepted request must be continued, responded to, or aborted; otherwise it remains stalled. The example below makes a decision for every request. Adapt the resource list to the target site rather than assuming that blocking the same types is safe everywhere.

4. Evaluate the lighter headless mode only when it fits

Puppeteer launches in headless mode by default. Its headless guide describes headless: 'shell' as an option for using chrome-headless-shell, which can be more performant for automation when the full Chrome feature set is not needed. The guide also cautions that chrome-headless-shell does not completely match regular Chrome’s behavior. Test the pages and browser features your job actually depends on before switching.

5. Reuse the browser, then recycle based on measurements

Keeping one browser open avoids repeated browser startup, but a long run may accumulate site state, caches, extensions, or application-level references. Record browser RSS through the operating system or your process supervisor. If memory continues to grow after pages close, or approaches a container or host limit, recycle the browser between batches and verify that the job recovers cleanly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose a restart threshold from observed behavior and available memory, not an arbitrary page count. Puppeteer’s official documentation does not prescribe a universal RSS threshold or number of jobs after which to restart.

A bounded Puppeteer example

This ES module uses one browser, a fixed number of workers, page-level cleanup, and optional resource filtering. Install Puppeteer in the project with npm install puppeteer, save this as capture.mjs, and run it with node capture.mjs. Replace the sample URLs with the URLs your job needs.

import puppeteer from 'puppeteer';

const urls = [
  'https://example.com/',
  'https://example.org/',
];
const concurrency = 2; // Tune against measured memory and throughput.
const skipResourceTypes = new Set(['image', 'font', 'media']);

const browser = await puppeteer.launch({
  headless: 'shell', // Use only if the workload works correctly in this mode.
});
let next = 0;
const results = [];

async function worker() {
  while (true) {
    const index = next++;
    if (index >= urls.length) return;

    const url = urls[index];
    const page = await browser.newPage();
    try {
      await page.setRequestInterception(true);
      page.on('request', request => {
        const action = skipResourceTypes.has(request.resourceType())
          ? request.abort()
          : request.continue();
        action.catch(() => {}); // A request may already have finished.
      });

      const response = await page.goto(url, {
        waitUntil: 'domcontentloaded',
        timeout: 30000,
      });
      const title = await page.title();
      results[index] = {
        url,
        status: response?.status() ?? null,
        title,
      };
    } catch (error) {
      results[index] = { url, error: String(error) };
    } finally {
      await page.close();
    }
  }
}

try {
  await Promise.all(
    Array.from({ length: Math.min(concurrency, urls.length) }, () => worker()),
  );
  console.log(results);
} finally {
  await browser.close();
}

The sample catches per-URL failures so one failed navigation does not stop other workers. It closes each page in finally, then closes the browser even if the job fails. For a task that needs images, fonts, or media, remove those types from skipResourceTypes; for a page requiring scripts or stylesheets, the example already allows them through. Change the navigation wait condition only if your task needs a different point in the page lifecycle.

How to measure before changing settings

Run a representative URL mix at one concurrency level and record measurements at consistent points, such as before a batch, at peak activity, and after pages close. Compare changes one at a time so you can tell whether an adjustment reduced memory, changed correctness, or simply lowered throughput.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
  • Track RSS for the Node parent and browser child processes, rather than relying only on Node heap statistics.
  • Record active page and context counts alongside RSS so memory changes can be interpreted against the amount of live work.
  • Capture navigation duration and failure rate; blocking resources or changing browser mode can affect page behavior.
  • Compare pages completed per minute at a fixed memory budget when tuning the worker count.
  • Repeat with URLs that reflect production pages, including their scripts, media, and authentication needs.

This approach lets you select a concurrency limit and recycling policy for your own machine and URL mix. It avoids treating one successful run as a general RAM-per-page benchmark.

Common Puppeteer memory problems and fixes

RSS rises as more URLs are processed

First check whether pages are being closed after both successful and failed tasks. Then inspect references retained in arrays, listeners, or closures, and confirm that the active-page limit is actually enforced. If pages close but browser RSS remains high or grows over long batches, test controlled browser recycling and monitor whether it restores the process to an acceptable operating range.

Pages hang after enabling interception

Every intercepted request needs an explicit outcome. Ensure the handler calls continue(), respond(), or abort() for each request, including resource types you intend to keep. A request left undecided remains stalled.

Extracted pages are incomplete or behave differently

Resource filtering and headless-shell mode can change what the page renders or how it behaves. Allow the resource types the site needs, including scripts, stylesheets, fonts, and images where relevant. If the task needs full Chrome behavior, use the regular headless browser mode instead of assuming shell mode is interchangeable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Node reports an out-of-memory error, but browser RSS is also high

Separate the two measurements. Node heap pressure can point to application objects or retained results; high Chromium child-process RSS is a browser-side concern. A Node heap flag does not cap Chromium’s renderer memory, so identify which process is growing before changing limits.

The browser cannot launch or deployment runs out of storage

Check that the configured executable is available and that the cache and temporary locations are writable and have adequate disk space. Puppeteer’s executable and cache settings can address packaging and storage problems, but they do not reduce renderer RAM by themselves.

A suggested Chromium flag appears to promise a memory fix

Do not assume that --disable-dev-shm-usage, --single-process, or --no-sandbox universally lowers Puppeteer RSS. The official Puppeteer material cited here does not establish those claims. Evaluate deployment flags against the specific failure they are meant to solve and measure the result; do not substitute them for limiting live work and closing pages.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your task is simply to obtain screenshots rather than control a Puppeteer browser yourself, ScreenshotNeo is a website screenshot API and MCP server for developers. A single GET request can return an image or PDF without you launching and managing a local Puppeteer browser. See the ScreenshotNeo API documentation for request options.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL:

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

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners are accepted before capture, and more than 60 known consent platforms, newsletter popups, and chat widgets are removed; each step can be turned off.
  • Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client.
  • The Free plan includes 1,000 screenshots per month with no card required; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.