Use one Lambda invocation to launch a Lambda-compatible Chromium, create one Puppeteer Page per URL, and process those pages through a small worker pool. Keep concurrency bounded, close every page, and close the browser in a finally block. There is no universal safe tab count: memory, page weight, navigation time, screenshot size, and the sites you visit determine the limit. For larger independent batches, fan URLs out to separate invocations instead of continually increasing in-browser concurrency.
What you need
- A Node.js Lambda function packaged with
puppeteer-core. - A Chromium build intended for serverless deployment, such as
@sparticuz/chromium. - Puppeteer and Chromium versions that are compatible with each other.
- A Lambda architecture supported by the exact Chromium build you select. Some published builds are x86_64-only; do not assume every build supports arm64.
- Enough deployment space for the browser. The Sparticuz project notes that its compressed browser file is over 50 MB and documents a
-minpackage for environments with tighter package limits; that option requires you to provide the compressed browser files separately.
Install the packages in your project (choose versions using the current compatibility guidance for the selected Chromium build):
npm install puppeteer-core @sparticuz/chromium
Do not copy a version number from an old tutorial without checking the current Puppeteer/Chromium support information. A mismatch commonly appears as an executable-launch or protocol error rather than a clear version warning.
Capture several URLs in one invocation
The following handler accepts an event shaped like {"urls":["https://example.com", "https://example.org"]}. It starts one browser, limits simultaneous pages to three as a conservative starting point, preserves input order, and returns PNG buffers encoded by Lambda’s JSON response machinery only if you add your own serialization. In a real service, upload each buffer to S3 or another destination rather than returning large binary data in the invocation response.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
import chromium from '@sparticuz/chromium';
import puppeteer from 'puppeteer-core';
export const handler = async (event) => {
const urls = Array.isArray(event.urls) ? event.urls : [];
if (urls.length === 0) {
throw new Error('event.urls must contain at least one URL');
}
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(),
headless: chromium.headless,
});
try {
// Starting point only; validate with representative pages and your Lambda settings.
const concurrency = Math.min(3, urls.length);
const results = new Array(urls.length);
let next = 0;
await Promise.all(Array.from({ length: concurrency }, async () => {
while (true) {
const index = next++;
if (index >= urls.length) return;
const page = await browser.newPage();
try {
await page.goto(urls[index], {
waitUntil: 'networkidle0',
timeout: 45_000,
});
results[index] = await page.screenshot({ type: 'png' });
} finally {
await page.close();
}
}
}));
return {
count: results.length,
images: results.map((buffer) => buffer.toString('base64')),
};
} finally {
await browser.close();
}
};
The worker pool is deliberately bounded. Each worker owns a page at a time, so a slow or heavy URL does not create an unbounded number of tabs. The value of three is an illustrative starting point, not a measured guarantee. Test with your actual pages, screenshot dimensions, output destination, memory setting, and timeout.
Use a less strict navigation condition when appropriate
networkidle0 waits for no active network connections. Analytics, advertisements, sockets, or long-polling applications can prevent that condition from being reached. For those pages, use domcontentloaded or load, then wait for a known selector or a short, explicit delay:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45_000 });
await page.waitForSelector('main', { timeout: 15_000 });
A selector wait is usually more meaningful than an arbitrary delay when the page has a reliable readiness element.
Capture one element or configure the page
Before the screenshot, you can set a viewport, emulate a device, hide selectors, or capture a specific element:
Recommended Free Tools
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.addStyleTag({ content: '.cookie-banner, .chat-widget { display:none !important; }' });
const card = await page.$('#report');
if (!card) throw new Error('Required #report element was not found');
results[index] = await card.screenshot({ type: 'png' });
Keep page-specific actions inside the worker’s try block so the finally clause still closes the page when a selector or screenshot fails.
Return files safely
Screenshot buffers can be large. A synchronous response containing many base64 images increases memory and payload size. A more durable pattern is to write each result to object storage as soon as it is produced and return keys, statuses, and errors. The AWS fan-out example for Puppeteer uses S3 for this purpose. Include the URL index in the object key so results remain associated with the input even when completion order differs.
For partial failure handling, store a per-URL record such as {url, status:"ok", key} or {url, status:"error", message}. Do not discard successful captures merely because another page timed out.
Choose memory, timeout, and concurrency together
Lambda allocates CPU in proportion to the memory setting. More tabs therefore consume both additional memory and CPU, while large DOMs, images, fonts, and JavaScript increase pressure further. Lambda function timeouts can be configured from 1 to 900 seconds (15 minutes). Navigation, waiting, screenshot encoding, uploads, retries, and cleanup must all fit within that limit.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →| Workload characteristic | What to adjust | Why |
|---|---|---|
| Heavy pages or full-page screenshots | Increase memory first; retest worker count | Browser processes and rasterization need more memory and CPU |
| Slow or variable target sites | Set explicit navigation/selector timeouts and a realistic function timeout | One stalled page can consume the invocation budget |
| Many small, independent URLs | Use bounded in-browser workers or fan out to invocations | Horizontal distribution can isolate failures and shorten wall-clock time |
| Large output or uploads | Upload per result and limit simultaneous uploads | Buffers and network throughput compete with browser memory |
Measure maximum memory used and duration with representative pages, including your largest expected HTML and image payloads. AWS Lambda best-practice guidance recommends load testing rather than selecting settings from a generic rule. Also account for upstream and downstream throughput: raising Lambda concurrency can rate-limit or overload the sites you visit and the storage service receiving screenshots.
One browser with multiple pages versus fan-out Lambdas
| Decision factor | Multiple tabs in one invocation | Separate invocations |
|---|---|---|
| Browser startup | One startup amortized across URLs | Startup occurs per invocation |
| Failure isolation | A browser crash can affect every active page | One URL can fail without taking unrelated captures with it |
| Memory and timeout | All active pages share one invocation budget | Each invocation gets its own budget |
| Scheduling | Simple worker pool and ordered results | Requires a dispatcher, queue, or asynchronous invocation tracking |
| Target-site pressure | Easy to control with one pool | Overall concurrency must be limited across all invocations |
Use several pages in one browser when the batch is modest, URLs are related, and sharing startup cost is valuable. Distribute work when captures are independent, the list is large, individual pages are unpredictable, or one failure must not jeopardize the rest. An AWS Architecture Blog design (published 31 March 2021) demonstrates a fan-out function that asynchronously invokes a Puppeteer function for each URL and writes screenshots to S3. Treat that post as an architecture pattern, not as a current guarantee about package versions or service configuration.
Rank #3
A practical fan-out shape
- Validate and persist the URL list with a batch identifier.
- Dispatch one asynchronous Lambda invocation per URL, or enqueue one message per URL.
- Have the worker launch Chromium, capture one page, write the object, and record success or failure.
- Apply a global concurrency limit and retries with backoff so the target site and downstream services are not flooded.
- Mark the batch complete when every item has a terminal status.
Deployment and compatibility checks
Verify the exact browser build
Pass the selected package’s documented args, defaultViewport, headless setting, and executable path to puppeteer.launch. The executable path is resolved at runtime in the example. Confirm that the build supports your Lambda architecture; the architecture note attached to one Serverless example is specific to that Chromium build, not to Lambda as a whole.
Check package-size limits
If the complete compressed browser package exceeds your deployment method’s limit, use the package’s documented minimal distribution and provide its compressed browser files through the supported mechanism. Recheck the uncompressed size as well as the upload size; they are separate constraints.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Do not confuse Synthetics with a normal function
CloudWatch Synthetics canaries support Puppeteer screenshots and multiple tabs, but each runtime release bundles particular Puppeteer and Chromium versions. Those bundled versions do not define the versions in your standalone Lambda package.
Reliability and error handling
Navigation timeout
Cause: the site is slow, never becomes idle, or blocks the request. Fix: choose domcontentloaded or load, wait for a meaningful selector, set an explicit timeout, and record the URL as failed when the deadline is exceeded.
Chromium cannot launch
Cause: an incompatible Puppeteer/Chromium pair, wrong executable path, missing launch arguments, unsupported architecture, or an incomplete browser package. Fix: verify the selected package’s current compatibility guidance, architecture, packaged files, and runtime permissions.
Out-of-memory termination
Cause: too many simultaneous pages, unusually large documents, full-page rasterization, or buffers retained while uploads wait. Fix: lower worker count, raise memory, close pages immediately, stream or upload results sooner, and test the largest expected pages.
Blank or incomplete screenshots
Cause: capture happened before application content rendered, a required selector was absent, or a consent/interstitial page replaced the intended content. Fix: wait for an application-specific selector, inspect the final URL and page content on failure, and add controlled authentication or cookie setup where required.
Browser remains open after an exception
Cause: cleanup was placed only after successful work. Fix: keep browser.close() in the outer finally block and page.close() in each worker’s inner finally.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF, so you can process a URL list without packaging Chromium in Lambda. Its cleanup steps accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for parameters and batch options. A single request looks like this:
Free tools Windows power users keep installed
One-click scans. No signup required.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python:
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)
Equivalent 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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
Every plan includes the same feature set: full-page and element capture, device and viewport controls, custom CSS and JavaScript, waits, request blocking, headers and cookies, geolocation and timezone, resizing, caching, signed links, asynchronous jobs and webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Pricing is Free for 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to start.
Best Value
FAQ
Can I reuse one Page object for every URL?
Yes, but separate pages make per-URL cleanup and failures easier to isolate. If you reuse a page, clear state deliberately between navigations and ensure no task is still using it.
Should I retry every failed navigation?
No. Retry transient network failures with a cap and backoff; repeated retries will not fix a deterministic 404, authentication failure, selector mismatch, or blocked request.
Can I guarantee completion before the 15-minute limit?
No. The 900-second maximum is a configuration limit, not a throughput guarantee. Measure your slowest representative workload and leave time for uploads and cleanup.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallFrequently Asked Questions
Can I reuse one Page object for every URL?
Yes, but separate pages make per-URL cleanup and failures easier to isolate. If you reuse a page, clear state deliberately between navigations and ensure no task is still using it.
Should I retry every failed navigation?
No. Retry transient network failures with a cap and backoff; repeated retries will not fix a deterministic 404, authentication failure, selector mismatch, or blocked request.
Can I guarantee completion before the 15-minute limit?
No. The 900-second maximum is a configuration limit, not a throughput guarantee. Measure your slowest representative workload and leave time for uploads and cleanup.
The Bottom Line
Start with puppeteer-core, a compatible Lambda Chromium build, and a small measured worker pool. Move large or failure-sensitive URL batches to separate invocations, and size memory, timeout, and global concurrency from load tests rather than a fixed tab-count rule.
Quick Recap
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.

