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.
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.
- Start the browser once under service-level ownership.
- For each job, create a page or an isolated context and page.
- Set that job’s viewport and navigate to its URL.
- Await the screenshot and handle the resulting bytes or file.
- Close the page or context in a
finallyblock. - 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
waitUntilcondition 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: truewhen 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
finallyblocks. 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.


