ScreenshotNeo

BlogHow-to

How to Reuse a Puppeteer Browser for Multiple Screenshots

Reuse one Puppeteer browser across screenshot jobs, choose pages or isolated contexts, and clean up reliably with runnable examples and troubleshooting.

By the ScreenshotNeo team4 October 20267 min read

Launch or connect to one Puppeteer Browser, then reuse it for multiple screenshot jobs. For independent captures, create a fresh Page for each job, set its viewport, navigate, await page.screenshot(), and close the page. Close the browser when the batch or owning service is finished. Use a separate browser context when jobs must not share cookies or cache.

This keeps browser lifetime separate from page lifetime. Puppeteer documents browser and page creation, screenshots, and cleanup in its screenshots guide and Browser API.

1. Reuse one browser for a batch

Install Puppeteer in a Node.js project with npm install puppeteer. This example processes a list of jobs sequentially, uses each job’s dimensions, and ensures pages and the browser are closed even if navigation or capture fails.

import puppeteer from 'puppeteer';

const jobs = [
  { url: 'https://example.com', outputPath: 'example.png', width: 1280, height: 800 },
  { url: 'https://stripe.com', outputPath: 'stripe.png', width: 1440, height: 900 },
];

const browser = await puppeteer.launch();
try {
  for (const job of jobs) {
    const page = await browser.newPage();
    try {
      await page.setViewport({ width: job.width, height: job.height });
      await page.goto(job.url, { waitUntil: 'networkidle2' });
      await page.screenshot({ path: job.outputPath, fullPage: true });
    } finally {
      await page.close();
    }
  }
} finally {
  await browser.close();
}

The example uses networkidle2 as one possible navigation condition; it is not right for every site. Choose a condition that fits the page, or wait for a known selector when the page has a specific readiness signal. Do not assume network idle means every image or client-rendered component is ready.

2. Choose pages or isolated browser contexts

Use one page per job when shared session state is acceptable

browser.newPage() creates a page in the default browser context. This is a clear option for unrelated captures when sharing context state is acceptable. Close the page when its screenshot work ends so it does not accumulate across requests.

Use a context per job when cookies and cache must be separated

Create a separate BrowserContext when each task should have its own cookies and cache. Puppeteer documents that contexts do not share cookies or cache; closing a context closes its pages. This provides a session and storage boundary, not total process or machine isolation. The default context cannot be closed. See the BrowserContext API and createBrowserContext() reference.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const context = await browser.createBrowserContext();
  try {
    const page = await context.newPage();
    await page.setViewport({ width: 1280, height: 800 });
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
    await page.screenshot({ path: 'isolated.png', fullPage: true });
  } finally {
    await context.close();
  }
} finally {
  await browser.close();
}
Pattern Use it when Cleanup
One browser, new page per job Jobs may share the default context’s session state Close each page; close browser at batch or service shutdown
One browser, new context per job Jobs should not share cookies or cache Close each context; it closes the pages it owns
New browser per job A separate browser lifecycle is needed for the workload Close that browser and its pages after the job

3. Manage screenshot results and cleanup

page.screenshot() can save to a path or return screenshot data, depending on the options. Await the capture before cleaning up so the output is available before the page or owning context closes. Puppeteer’s screenshot API also documents coordination with page creation and closing while an active screenshot is finishing. That does not mean all operations on a page are serialized automatically. See Page.screenshot().

const bytes = await page.screenshot({ type: 'png' });
// Persist bytes with your chosen storage client or filesystem API.

Set viewport dimensions per page before navigation when rendering depends on the viewport. One browser can host pages with different viewport sizes. To inspect ownership, browser.pages() lists open browser pages and context.pages() lists pages in a context. A fresh page per job usually makes it easiest to see who owns each page and when to close it.

4. Reuse a browser in a long-running service

If a service keeps a browser alive across requests, give it a clear owner and shutdown path. The service can launch one browser during startup, create and close request-owned pages or contexts for each capture, and close the browser during shutdown. Avoid closing a shared browser from an individual request handler while other jobs may still depend on it.

  1. Start the browser once under service-level ownership.
  2. For each job, create a page or an isolated context and page.
  3. Set that job’s viewport and navigate to its URL.
  4. Await the screenshot and handle the resulting bytes or file.
  5. Close the page or context in a finally block.
  6. On service shutdown, close the browser.

This lifecycle follows Puppeteer’s documented close behavior; it is an operational pattern. The docs do not establish a universal safe concurrency level or a fixed speed improvement.

5. Navigation, reliability, and performance choices

  • Navigation readiness: Select a waitUntil condition appropriate to the site. A page that keeps network connections open may not become idle as expected; a page that renders content after navigation may need an explicit selector or application-specific readiness check.
  • Viewport: Set the viewport before navigation if responsive layout or page scripts depend on it. Different pages in the same browser can use different dimensions.
  • Full-page capture: Use fullPage: true when the whole document is needed. For very long or highly dynamic pages, inspect the output for content that loads only after scrolling or changes during capture.
  • Sequential versus concurrent jobs: The batch above is sequential, making resource ownership and failures straightforward. If adding concurrency, bound the number of active pages for your environment and measure memory, time, and failure rates on the actual workload. The cited Puppeteer documentation gives API behavior, not a benchmark or universal limit.
  • Browser reuse: Reusing a browser avoids making browser startup part of every job’s lifecycle, but actual throughput and resource use depend on pages and workload. Measure before choosing a concurrency policy.
  • Reliability: Put page or context cleanup in finally blocks. Keep browser shutdown owned by the batch or service, so an exception in one capture does not silently leave its page open or accidentally close a browser needed by other work.
  • Cost: Puppeteer itself does not define a universal per-screenshot price in these sources. Account for the compute and infrastructure used to run Chromium, plus your own storage and network costs. Compare those costs against the volume and maintenance needs of your capture workflow.

6. Troubleshooting

Symptom Likely cause Fix
Browser process remains after a batch An error bypassed browser cleanup, or the service has no shutdown path Put batch work inside try/finally and call browser.close() when its owner is done.
Pages or memory accumulate over time Pages or contexts are not closed after each job Close request-owned pages in finally, or close the job context to close its pages.
One URL appears to retain another job’s state Jobs share a browser context and therefore may share session state Use a separate BrowserContext for jobs that should not share cookies or cache.
Navigation waits too long or times out The selected readiness condition does not match the site’s network behavior Choose a suitable navigation condition or wait for a page-specific selector/readiness signal.
Screenshot has the wrong responsive layout Viewport was missing, incorrect, or set after navigation Set the intended viewport before navigating, and use the correct dimensions for each job.
Screenshot is blank or misses late content Capture began before relevant client-side content or images were ready Wait for a known selector or application-specific condition before calling screenshot().
Capture fails while a page is being closed Cleanup began before screenshot work completed or another owner closed the page Await page.screenshot() before cleanup and keep page ownership local to the job.

7. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF; 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,
)
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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

8. FAQ

Should I create a new page for every screenshot?

For independent jobs, a new page per job gives clear ownership and cleanup. Reuse the browser that owns those pages.

Do I need a new browser context for every URL?

No. Use a separate context when the jobs need separate cookies or cache. Otherwise, a page in the default context is simpler.

Can pages in one browser have different dimensions?

Yes. Set the viewport on each page for its capture before navigating.

Does reusing a browser guarantee a particular speedup?

No fixed speedup is established by the cited API documentation. Measure on your own sites and deployment environment.

Primary Puppeteer references