Use one Puppeteer browser for the batch, navigate to each URL, and save each viewport screenshot to a unique, filesystem-safe filename. Puppeteer supplies the browser and screenshot operations; the URL loop, filename mapping, and per-URL error handling are application logic you add.
Capture a thumbnail for every URL
This Node.js script reads URLs from an array, captures the initial viewport, and writes one PNG per successful URL. It uses a single browser process, a fresh page per URL, and a stable index-plus-host filename so URL characters cannot become unsafe path characters.
As an Amazon Associate I earn from qualifying purchases.
const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');
const urls = [
'https://example.com/',
'https://www.wikipedia.org/',
];
const outputDir = path.resolve('thumbnails');
function outputName(url, index) {
const parsed = new URL(url);
const host = parsed.hostname.replace(/[^a-zA-Z0-9.-]/g, '_');
return `${String(index + 1).padStart(3, '0')}-${host}.png`;
}
async function main() {
await fs.mkdir(outputDir, { recursive: true });
const browser = await puppeteer.launch({ headless: true });
const results = [];
try {
for (const [index, value] of urls.entries()) {
let page;
try {
const target = new URL(value);
if (!['http:', 'https:'].includes(target.protocol)) {
throw new Error(`Unsupported URL protocol: ${target.protocol}`);
}
page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(target.href, {
waitUntil: 'domcontentloaded',
timeout: 30000,
});
const filename = outputName(target.href, index);
const outputPath = path.join(outputDir, filename);
await page.screenshot({ path: outputPath, type: 'png' });
results.push({ url: target.href, filename, status: 'saved' });
console.log(`Saved ${target.href} -> ${outputPath}`);
} catch (error) {
results.push({ url: value, status: 'failed', error: error.message });
console.error(`Failed ${value}: ${error.message}`);
} finally {
if (page) await page.close();
}
}
} finally {
await browser.close();
}
await fs.writeFile(
path.join(outputDir, 'manifest.json'),
JSON.stringify(results, null, 2)
);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Install Puppeteer in your project with npm install puppeteer, save the code as a CommonJS file such as capture.js, and run node capture.js. Puppeteer’s package normally downloads a compatible browser during installation; follow its installation documentation if your environment uses a separately managed browser.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →What the script produces
The thumbnails directory contains numbered PNG files and a manifest.json mapping each input URL to its outcome. The index ensures that two URLs on the same host do not overwrite each other. If your downstream system needs a more descriptive name, include a sanitized path segment or a hash of the complete URL, while retaining the original URL in the manifest.
#1 Best Overall
Choose when the page is ready
The example waits for domcontentloaded, which avoids waiting for every subresource to finish. Some sites need more time for client-rendered content, while others keep network requests open and may not reach a network-idle state. Puppeteer’s Screenshots guide demonstrates waitUntil: 'networkidle2' before capture, but that is an example rather than a universal readiness guarantee. See the Puppeteer Screenshots guide.
domcontentloaded: a practical starting point when the document has loaded but images or scripts may still be settling.load: use when the page’s normal load event is a useful signal for your sites.networkidle2: use when you specifically want network activity to quiet down; persistent connections or polling can make an idle condition unsuitable.page.waitForSelector('...'): wait for a known content element when a specific component indicates readiness.page.waitForTimeout(...): use a bounded delay only when the page has a known rendering delay and no better signal.
You can combine navigation with a selector wait, for example:
await page.goto(target.href, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForSelector('main article', { timeout: 10000 });
A timeout is important in batch jobs: without one, a single slow site can hold up later URLs. A readiness signal also cannot guarantee that every lazy-loaded image or third-party component has finished rendering.
Rank #2
- Intuitive interface of a conventional FTP client
- Easy and Reliable FTP Site Maintenance.
- FTP Automation and Synchronization
Set thumbnail dimensions and screenshot options
A thumbnail normally means the visible viewport. The script sets a 1280 × 800 viewport before navigation; change those dimensions to match the previews you need. Puppeteer’s Page.screenshot() captures the page, and its fullPage option defaults to false. The ScreenshotOptions API reference documents the capture settings.
pathwrites the image to a file. Without it, the screenshot call returns image data instead.fullPage: truecaptures the full document rather than just the viewport; use it for page snapshots, not compact above-the-fold thumbnails.cliplimits the capture to a specified region.typeselects an image format such as PNG, JPEG, or WebP, subject to the supported options in the API.qualitycontrols quality for supported lossy formats; it does not apply to PNG.omitBackground: truerequests a transparent background where supported by the capture.
For an image of one component rather than the whole page, use an element handle’s screenshot() method. Puppeteer’s guide notes that it attempts to scroll an element into view if it is hidden. This produces a component preview, not a page thumbnail.
Use a safe naming scheme for URL-derived files
Do not put the raw URL directly into a file path. URLs may contain slashes, query strings, fragments, and characters with special meaning to a filesystem; different URLs can also collapse to the same sanitized name. The script avoids path traversal by deriving the output name from a parsed hostname and adding the input index.
Rank #3
If the batch is rerun and filenames need to remain stable even when input order changes, derive a short hash from the normalized full URL instead of relying on an index. Keep the URL-to-file mapping in a manifest either way. Consider how to handle duplicate input URLs explicitly: preserve both captures with distinct indices, or deduplicate before the loop if only one image per unique URL is wanted.
Process larger lists reliably
The sequential loop is intentionally conservative: it limits browser resource use and makes individual failures easy to associate with an input. For more throughput, use a bounded worker pool rather than launching an unbounded number of pages. There is no universally safe concurrency setting established by Puppeteer’s screenshot documentation; choose a limit based on the machine, page weight, and target-site constraints, then observe memory use and failures.
- Reuse the launched browser for the batch; close each page after its capture and close the browser in a
finallyblock. - Record success or failure for every URL so one navigation failure does not erase the rest of the batch outcome.
- For very large input lists, consider periodic progress output and writing results incrementally so a process interruption does not lose the entire manifest.
- Validate allowed URL schemes and, for untrusted inputs, apply your own network-access policy. A screenshot script that visits arbitrary URLs can reach internal services if run in an insufficiently restricted environment.
Troubleshoot failed or unexpected captures
The browser does not launch
Check that Puppeteer installed successfully and that its expected browser is available. If your deployment manages Chrome separately, configure Puppeteer for that environment and verify the executable and required system libraries. Installation and browser configuration vary by platform.
Rank #4
A URL times out
The site may be slow, unreachable, or keeping requests active. Increase the navigation timeout only if the batch can afford the wait; otherwise keep the timeout bounded, record the failure, and continue. If using a network-idle condition, switch to a more suitable navigation event or wait for a page-specific selector.
The screenshot is blank or misses content
Confirm navigation reached the intended page, then wait for the content element or a bounded rendering delay before capture. A document-loaded signal does not mean every client-rendered element or lazy image is ready. Also verify the configured viewport and that the page is not showing an interstitial or access challenge.
Files are overwritten
Make sure each output path is unique for the batch. Hostnames alone are insufficient when capturing multiple paths on a host; append an index or URL hash and preserve the source URL in the manifest.
Best Value
A capture unexpectedly includes the whole page
Check whether fullPage: true is enabled in your options. For a standard viewport thumbnail, leave it false or omit the option, since false is the documented default.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its clean-shot steps can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
One request captures one URL; call it once for each item in your own URL loop. The response body is the image, so save it directly to the desired filename. See the ScreenshotNeo API documentation for request options.
Recommended Free Tools
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo also provides an MCP server for AI agents using Claude, Cursor, or another MCP client, with take_screenshot, get_page_info, and capture_pdf tools. Its free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.
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.

