Free tools Windows power users keep installed
One-click scans. No signup required.
Set clearImageCache: false and keep the same html2canvas cache state available for every capture. Do not create a new cache inside the loop. If your installed release exposes maxCacheSize, use it to limit memory while allowing least-recently-used images to be evicted. This preserves reuse without wiping the cache after each frame.
Repeated requests can also come from changing image URLs, cloned DOM content, cross-origin redirects, or a wrapper that rebuilds rendering state. The sections below show how to isolate each cause without changing the live page.
The cache setting that stops repeated image loads
html2canvas has an image cache intended to persist between calls. The important setting is:
clearImageCache: false
Leaving this option false keeps cached images available for later captures. Setting it to true in a loop clears the reusable image state, so the next iteration must fetch or decode those resources again. The configuration guidance also warns against enabling cache clearing when captures share a cache concurrently.
#1 Best Overall
A minimal sequential loop looks like this:
async function captureFrames(frames) {
const results = [];
for (const frame of frames) {
const canvas = await html2canvas(frame.element, {
clearImageCache: false
});
results.push(canvas);
}
return results;
}
Await each capture before starting the next one unless your application has a cache design that is explicitly safe for concurrent rendering. Sequential work makes it easier to verify that the same cache is being reused and avoids two renders changing shared state at the same time.
Keep cache state outside the loop
html2canvas creates a rendering context for each call and passes resource options, plus an optional cache, into that context. If application code, a framework hook, or a helper constructs a new cache for every iteration, the cache cannot provide reuse even when clearImageCache is false.
Some releases expose a cache-injection API. In a release that publicly exposes CacheStorage, the pattern is:
const sharedCache = new CacheStorage(); // Verify this API in your installed version.
async function captureFrames(frames) {
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);
}
}
The exact cache API is version-dependent. Do not copy CacheStorage or the cache option unless the public options for your installed package confirm them. The portable part of the fix is to leave clearImageCache false and to stop wrappers from constructing fresh cache state inside the loop.
Why html2canvas appears to reload resources
A new cache or context is created every iteration
Inspect the complete call path, not only the line containing html2canvas(). A component can recreate a renderer, cache, or options object in an effect or callback even when the loop itself looks stable. Put long-lived cache state at application scope, or at least outside the loop and outside code that runs once per frame.
Rank #2
The requested URL changes
An image URL with a timestamp, query-string cache buster, signed expiry, or changing CSS background-image value is a different cache key. The browser and html2canvas cannot reuse an entry whose URL changes. Compare the exact request URL in the Network panel for the first and second iterations.
The clone contains volatile or unnecessary nodes
Each render uses a cloned document. Advertising slots, live chat, rotating avatars, and animated widgets can introduce new resources on every clone. Use onclone to alter only the cloned document, so the live page remains untouched:
const canvas = await html2canvas(element, {
clearImageCache: false,
onclone: (clonedDocument) => {
clonedDocument.querySelectorAll('.chat-widget, .ad-slot')
.forEach((node) => node.remove());
}
});
onclone is called for the document used for rendering. It can remove nodes, replace volatile URLs, or otherwise make each iteration’s resource set deterministic.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Reduce the resource set before rendering
Ignore elements that never belong in the image
Use the ignoreElements predicate when the decision is computed in JavaScript:
const canvas = await html2canvas(element, {
clearImageCache: false,
ignoreElements: (node) => node.matches('.live-only, .tracking-pixel')
});
You can also mark elements declaratively:
<div class="tracking-pixel" data-html2canvas-ignore="true"></div>
Both mechanisms reduce the resources html2canvas needs to inspect. They are useful for decorative or dynamic content that does not need to appear in the capture.
Keep clone cleanup enabled
removeContainer defaults to true and removes temporary cloned DOM elements after rendering. Turning cleanup off does not stop network requests and can retain more DOM memory, so it is not a solution for repeated loading.
Cross-origin images, CORS, and redirects
html2canvas cannot bypass browser content-policy restrictions. Set useCORS: true only when the image server sends a suitable Access-Control-Allow-Origin response header:
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →const canvas = await html2canvas(element, {
clearImageCache: false,
useCORS: true
});
If the server does not grant access, configure a proxy that fetches the image through the page’s origin. The relevant defaults are useCORS: false, proxy: null, and an imageTimeout of 15,000 milliseconds.
Check the final URL after redirects
A URL that appears same-origin can redirect to a CDN. An html2canvas issue reports that origin classification may occur before the redirect, meaning useCORS might not be applied to the final CDN request. Treat this as a diagnostic case rather than relying on an unofficial workaround:
- Open the browser Network panel.
- Inspect the request’s redirect chain and final URL.
- Check the final response’s
Access-Control-Allow-Originheader. - Confirm whether the second loop iteration requests the same final URL.
If the final response is cross-origin without the required header, use a same-origin proxy or change the asset delivery configuration.
Rank #4
Choose a loop strategy that fits your constraints
| Strategy | Use it when | Trade-off |
|---|---|---|
| Shared cache, sequential captures | You need predictable reuse and straightforward debugging. | Captures run one at a time. |
Shared cache with maxCacheSize |
Your installed version exposes this option and a long-running process needs a memory ceiling. | Least-recently-used images can be evicted and later fetched again. |
| Clone filtering | Dynamic widgets or decorative assets change between frames. | Ignored nodes will not appear in the output. |
| CORS or same-origin proxy | Images come from another origin. | Requires server headers or proxy infrastructure. |
Do not clear the entire shared cache merely to control memory. Where supported, maxCacheSize provides bounded eviction instead of an all-at-once reset.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It accepts a URL and returns a PNG, JPEG, WebP, or PDF without requiring you to manage a browser loop. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for authentication and options. A single cURL request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in 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)
And in 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(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', data);
ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes its capture features, including full-page lazy-image loading, CSS-selector element capture, device presets, custom viewport and retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user-agent and authorization settings, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; yearly billing provides two months free. Create a free ScreenshotNeo account to try it without a card.
Troubleshooting repeated requests
| Symptom | Likely cause | Fix |
|---|---|---|
| Every iteration downloads every image | clearImageCache: true, or a cache is created inside the loop. |
Set it to false and move cache construction to stable application scope. Verify that your release supports cache injection. |
| Requests differ only by a query string | A timestamp, signature, or cache-busting parameter changes the URL. | Stabilize the URL in onclone when it is safe to do so. |
| Dynamic widgets keep appearing in the request log | The cloned document includes live or decorative nodes. | Remove them in onclone, use ignoreElements, or add data-html2canvas-ignore="true". |
| Images are blank or the canvas is tainted | The final image response is cross-origin without permission. | Check the final response headers; enable useCORS only with server cooperation, otherwise use a same-origin proxy. |
useCORS appears ineffective |
The URL redirects to a CDN whose origin was classified differently. | Inspect the redirect chain and final URL in Network tools, then verify headers on that final response. |
| Memory grows during a long run | The cache retains many images or cloned content is not cleaned up. | Keep removeContainer enabled and use maxCacheSize if your installed version exposes it. |
| An option works in one project but not another | html2canvas versions and forks expose different configuration surfaces. | Check the installed version and its public options before using CacheStorage, cache, or maxCacheSize. |
Performance and reliability checks
- Record the exact request URL, redirect chain, response status, response headers, and cache status for the first two iterations.
- Run a two-capture test with identical DOM and URLs before optimizing a larger batch.
- Measure memory while using a shared cache; reuse reduces network work but retained images still consume memory.
- Use sequential captures while diagnosing shared-state problems, then introduce concurrency only after confirming your version’s cache behavior.
- Keep the 15,000-millisecond default image timeout in mind when a slow or unreachable asset makes a loop appear stalled; set a different timeout only when your application can handle the resulting failure behavior.
- Do not confuse an HTTP cache hit with html2canvas cache reuse. The Network panel can show whether a request was sent, redirected, served from browser cache, or fetched again, while your code determines whether html2canvas’s own cache is retained.
FAQ
Will the html2canvas cache survive a page reload?
No. A reload creates a new JavaScript runtime. The shared-cache pattern applies to captures made during the same application lifetime; persistent reuse across reloads would require a separate browser or server-side caching layer.
Best Value
Is maxCacheSize available in every html2canvas release?
No. Its availability, like cache injection, depends on the installed version. Confirm the public configuration for that release before relying on it.
Should I use a monkey patch for CDN redirects?
No. Verify the final URL and CORS headers first. An issue report’s workaround is not an official API contract and can break when the library changes.
Frequently Asked Questions
Will the html2canvas cache survive a page reload?
No. A reload creates a new JavaScript runtime; the shared-cache pattern applies to captures made during the same application lifetime.
Is maxCacheSize available in every html2canvas release?
No. Its availability depends on the installed version, so confirm that release’s public configuration first.
Should I use a monkey patch for CDN redirects?
No. Verify the final URL and CORS headers first; an issue-report workaround is not an official API contract.
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.

