BATCH TUTORIAL

Capture a URL list without creating an uncontrolled request burst

Turn each authorized target into a durable job, limit workers, retry only transient failures, and preserve configuration with every result. A batch is an operational workflow, not Promise.all over unbounded input.

Queue
One job per target
Workers
Bounded concurrency
Progress
Observable outcomes
Install:queue → worker pool → validated storage
Auth:SNAPSHOT_SITE_API_KEY
Get started for free
Multiple URL screenshot queue
Snapshot Site themed multiple URL screenshot workflow illustration
Good fits
Content inventories and documentation archives
Responsive QA across a controlled route list
Scheduled monitoring of approved pages
Migration reviews comparing representative URLs

The URL list is input to a job system

A reliable batch validates scope, deduplicates targets, limits concurrency, tracks attempts, and records terminal results without restarting successful work.

1

Validated inventory

Normalize and approve targets before placing them into the queue.

2

Bounded workers

Set concurrency deliberately and reduce it when rate or downstream storage pressure appears.

3

Idempotent identity

Key each logical capture by source and specification so retries do not create ambiguity.

4

Progress model

Expose queued, running, ready, retrying, and terminal states with actionable diagnostics.

Implementation workflow

Build a multiple-URL capture runner

1

Normalize and deduplicate an authorized URL inventory

2

Create one logical job for each URL and capture specification

3

Process jobs through a small bounded worker pool

4

Persist results and resume only unfinished or retryable work

Multiple URL screenshot example

JavaScript

Process URLs with bounded workers

The worker count is explicit and the queue can later be replaced by the application's durable job system.

async function runBatch(urls, workerCount) {
  const queue = [...new Set(urls)].map((url) => ({ url }));
  const results = [];

  async function worker() {
    while (queue.length) {
      const job = queue.shift();
      try {
        results.push({
          url: job.url,
          status: "ready",
          result: await captureUrl(job.url),
        });
      } catch (error) {
        results.push({
          url: job.url,
          status: "failed",
          error: classify(error),
        });
      }
    }
  }

  await Promise.all(
    Array.from({ length: workerCount }, () => worker()),
  );
  return results;
}

A loop is not yet a batch system

The first prototype often reads URLs and starts every request. It works until the inventory grows, a worker stops, rate pressure appears, or storage fails near the end. A production batch needs per-target identity and state.

Normalize URLs, remove duplicates, and confirm authorization before queueing. Associate each target with its intended viewport, format, readiness, and output policy.

Bound pressure at the source

Control concurrency before requests leave the application. A small worker pool gives predictable resource use and a place to react to rate responses. Avoid using provider errors as the normal mechanism for shaping traffic.

Keep capture and artifact-download concurrency separate when storage or network capacity differs.

Make progress durable

Record each logical job as queued, running, ready, retrying, or terminal. A restarted process should resume unfinished work and skip validated artifacts. Use idempotent storage so a retry cannot silently overwrite another approved result.

Classify failures. Invalid URL and authentication errors need correction; temporary transport or rate conditions may justify delayed bounded retry.

For recurring inventories, connect the job creator to screenshot scheduling. The rate-limit guide covers backoff and concurrency in greater depth.

Screenshot Multiple URLs FAQ

Can I use Promise.all for every URL?

Only for a deliberately small bounded list. Uncontrolled Promise.all can create traffic bursts, rate responses, memory pressure, and difficult recovery.

How many workers should run?

Choose concurrency from account limits, observed latency, target policy, and storage capacity. Start conservatively and measure.

How should duplicate URLs be handled?

Normalize targets and define logical identity from source plus viewport, format, readiness, and revision so equivalent jobs can be reused.

Should a failed batch restart from the beginning?

No. Store per-job state and resume only unfinished or safely retryable work.

Which failures should be retried?

Retry transient transport, rate, or service conditions with backoff and a cap. Correct invalid input and authentication failures.

How can progress be reported?

Track counts and identities by queued, running, ready, retrying, and terminal state, plus the oldest unfinished work.

Should every URL use the same settings?

Only when their visual requirement is genuinely shared. Different templates may need separate readiness, viewport, or full-page policies.

How are artifacts organized?

Use stable application IDs and retain source, specification, capture time, status, and storage location with each result.

Run a small resumable batch first

Validate target scope, worker limits, per-page status, and restart behavior before expanding the URL inventory.