Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If html2canvas requests the same images on every iteration, keep one shared cache for the lifetime of the capture job and set clearImageCache: false. Do not create a new cache inside the loop. If memory must be bounded, use the version-supported maxCacheSize option rather than clearing the entire cache after every capture.

Why html2canvas loads images again

Each html2canvas() call creates a rendering context. Resource options and an optional image cache are passed into that context. A loop can therefore lose reuse in two ways: the code explicitly clears the cache, or a wrapper creates a fresh cache or rendering state for every iteration.

Look first for clearImageCache: true. The html2canvas configuration reference says to leave this setting false to keep images cached across calls, and warns against enabling it when a cache is shared by concurrent captures. Setting it to true in a loop deliberately discards the reusable image state before the next capture.

Even with clearing disabled, a new URL is a new cache key. Query-string cache busters, changing CSS background-image values, signed URLs, redirects and dynamically inserted image nodes can all make the next iteration request a different resource.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use one cache for sequential captures

Keep cache state outside the loop and pass the same instance to each call. The cache constructor and injection option are version-dependent, so confirm that your installed html2canvas release publicly exposes them before using this pattern. The stable setting is clearImageCache: false.

const sharedCache = new CacheStorage(); // only where your installed version exposes this API

for (const frame of frames) {
  const canvas = await html2canvas(frame.element, {
    cache: sharedCache,
    clearImageCache: false,
    maxCacheSize: 200,
    onclone: (clonedDocument) => {
      clonedDocument
        .querySelectorAll('[data-html2canvas-ignore="true"]')
        .forEach((node) => node.remove());
    }
  });

  consume(canvas);
}

This example is deliberately sequential: the next capture starts after the previous promise resolves. It gives the shared cache a stable owner and makes network-panel comparisons easier. If your release does not support cache, do not copy the constructor from another version or fork; keep clearImageCache: false and consult that release’s public options for the supported caching mechanism.

Do not construct the cache in the loop

This defeats reuse even though the option appears correct:

for (const frame of frames) {
  const cache = new CacheStorage();
  await html2canvas(frame.element, {
    cache,
    clearImageCache: false
  });
}

Search helper functions as well as the visible loop. A utility that silently allocates a cache, document clone manager or renderer on each call has the same effect as the bad example.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Limit memory without flushing every image

A long-running capture process can retain many decoded images. Where the installed version supports it, maxCacheSize provides a ceiling and allows least-recently-used entries to be evicted. That is different from clearing everything after every frame: frequently reused images can remain available while old entries make room for new ones.

Option Setting or default What it changes
clearImageCache false for reuse Preserves the shared image cache between calls. Setting it true in the loop removes the main benefit of sharing.
maxCacheSize Choose a limit where supported Bounds retained cache entries through least-recently-used eviction; availability is version-dependent.
removeContainer true by default Removes temporary cloned DOM after rendering. Disabling cleanup does not stop image requests and can retain more DOM memory.
useCORS false by default Requests cross-origin images with CORS when enabled, but only succeeds if the final response permits your origin.
proxy null by default Uses a same-origin proxy when direct cross-origin loading cannot satisfy browser content-policy rules.
imageTimeout 15,000 milliseconds Stops waiting for an image after the documented default timeout.

Choose a limit based on the number and dimensions of images your job actually captures. A small limit can cause older images to be fetched again later; an unlimited cache can increase memory pressure. Measure both network requests and process memory instead of assuming that a larger cache is always faster.

Make the cloned document stable

onclone runs against the document clone used for rendering, not the live page. Use it to remove volatile or nonessential nodes, replace changing URLs, or make each iteration’s resource set deterministic without modifying what users see.

Remove widgets and decorative nodes

For one-off exclusions, mark elements in the source markup:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<div data-html2canvas-ignore="true" class="live-chat">...</div>

html2canvas ignores that element during capture. You can also use the ignoreElements predicate when the rule belongs in JavaScript:

await html2canvas(element, {
  clearImageCache: false,
  ignoreElements: (node) => node.matches('.live-chat, .newsletter-popup')
});

Filtering reduces the resources html2canvas has to inspect, but it can reduce visual fidelity if the excluded node is part of the intended result. Keep the rule limited to items that should not appear in the image.

Normalize changing URLs in onclone

If every frame appends a timestamp or cache-busting query parameter, the browser and html2canvas see a different URL each time. In the clone, replace that volatile value with a stable URL when the image content is meant to remain the same:

const stableCaptureOptions = {
  clearImageCache: false,
  onclone: (doc) => {
    doc.querySelectorAll('img').forEach((img) => {
      const url = new URL(img.src, doc.baseURI);
      url.searchParams.delete('cacheBust');
      url.searchParams.delete('timestamp');
      img.src = url.href;
    });
  }
};

Apply the same reasoning to CSS background-image values and dynamically added nodes. Do not normalize a URL when its query parameters select genuinely different content.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Cross-origin images, CORS and redirects

html2canvas cannot bypass browser content-policy restrictions. Set useCORS: true only when the image server sends an appropriate Access-Control-Allow-Origin header. If it does not, fetch the image through a proxy that serves it from the page’s origin.

A URL that appears same-origin can still redirect to a CDN. An html2canvas issue reports that origin classification may happen before the redirect, so CORS handling might not be applied to the final CDN request. Treat this as a diagnostic possibility, not as an API guarantee or a reason to patch internals.

  1. Open the browser Network panel while capturing one frame.
  2. Inspect the final request URL, every redirect, the response status and the response’s Access-Control-Allow-Origin header.
  3. Compare those values on the second iteration. If the final host or query string changes, the cache key may also change.
  4. Use a same-origin proxy when the final response cannot authorize your page’s origin.

Sequential versus concurrent loops

Sequential captures are the safest place to start because one shared cache has a single, predictable owner. They also prevent your code from clearing a cache while another capture is using it.

If you need concurrency, verify the installed release’s cache behavior and synchronization guarantees first. Never combine shared-cache concurrency with clearImageCache: true; that setting can discard resources another capture still expects. A bounded cache may also evict an image before a concurrent job reuses it, so measure request counts under the actual workload.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Debugging checklist

  • Requests repeat immediately: search for clearImageCache: true in the call and in wrapper functions; change it to false or remove the override.
  • The cache option appears correct but nothing is reused: confirm that every iteration receives the same cache object and that the installed version supports cache injection.
  • URLs differ between frames: compare image src values, CSS background URLs and query parameters; stabilize only values that do not select different content.
  • Chat, ads or popups create extra requests: remove them in onclone, use ignoreElements, or add data-html2canvas-ignore="true".
  • Images are blank or the canvas is tainted: inspect the final response for Access-Control-Allow-Origin; enable useCORS only with server cooperation, otherwise use a same-origin proxy.
  • A same-origin URL fails after a redirect: inspect the CDN destination and its headers rather than only the original URL.
  • Memory grows during a long job: keep the shared cache, enable a supported maxCacheSize, and leave removeContainer at its default cleanup behavior unless you have a specific reason to retain clones.
  • Images time out: remember that the documented default imageTimeout is 15,000 ms; verify the resource’s response time and adjust the option only when a longer wait is justified.
  • Documentation does not match your code: check the exact installed html2canvas release. Options exposed by a fork or a newer version are not automatically available in yours.

Performance and reliability practices

Reuse stable URLs, remove resources that do not belong in the capture, and keep temporary containers cleaning up. These changes reduce both network work and clone overhead without changing the live page. Record the request URL, redirect chain, cache status and response headers for at least two successive iterations; a visual match alone does not prove that no request occurred.

For repeatable output, capture the same element dimensions and avoid changing layout while a render is in progress. If the page intentionally changes an image between frames, expect a new request or cache entry; caching cannot make different content identical.

Or skip the browser setup

For server-side or automated screenshots, ScreenshotNeo provides a website screenshot API and MCP server. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info and capture_pdf—work with Claude, Cursor and other MCP clients.

One GET request returns a PNG, JPEG, WebP or PDF. The parameter names used by other screenshot APIs also work, which can simplify migration. See the ScreenshotNeo documentation for the complete option list.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

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)

Node.js

import { writeFile } from 'node:fs/promises';

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(`ScreenshotNeo returned ${res.status}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and annual billing gives two months free. Create a free ScreenshotNeo account to try the endpoint.

Frequently Asked Questions

Will the shared html2canvas cache survive a page reload?

No. The in-memory cache belongs to the current JavaScript execution. A reload creates a new page and a new cache, so persistent reuse would require a separate browser or application-level storage design.

Does a browser HTTP 304 response prove html2canvas reused its image cache?

No. A 304 or memory-cache result describes browser networking, while html2canvas may still decode or process the image for its renderer. Check both the Network panel and the cache configuration.

Can I use one cache for unrelated pages?

Only when the cache API exposed by your installed version supports that ownership model and the resource keys are safe to share. Keep the cache scoped to a capture job unless the package documentation explicitly supports broader sharing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.