
Avoiding Duplicate Capture Costs: A Simple Client-Side Caching Pattern

Snapshot Site Team
09 Feb 2026 - 03 Mins read
It's easy for the same URL, with the same parameters, to get captured multiple times in a short window without anyone noticing — a support tool that re-captures a page every time someone opens a ticket referencing it, a dashboard widget that fires a fresh request on every page load, a retry that doesn't check whether the original request actually succeeded. None of this is a bug exactly. It's just requests happening without anything checking whether the same result already exists.
A small client-side cache, keyed by the request's actual parameters, fixes this without touching anything on the API side.
The Core Idea
Before making a request, compute a cache key from everything that affects the output — the URL, the format, the dimensions, and any other parameter that changes what gets rendered — and check whether a recent result for that exact key already exists before calling the API again.
const crypto = require("crypto");
function cacheKey(params) {
const normalized = JSON.stringify(params, Object.keys(params).sort());
return crypto.createHash("sha256").update(normalized).digest("hex");
}
async function cachedScreenshot(params, cache, ttlMs = 15 * 60 * 1000) {
const key = cacheKey(params);
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttlMs) {
return cached.result;
}
const response = await fetch("https://api.prod.ss.snapshot-site.com/api/v1/screenshot", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-snapshotsiteapi-key": "YOUR_API_KEY",
},
body: JSON.stringify(params),
});
const result = await response.json();
cache.set(key, { result, timestamp: Date.now() });
return result;
}
Object.keys(params).sort() before stringifying matters — without it, the same parameters in a different object key order would hash to a different cache key and silently defeat the cache.
Picking a TTL
The right cache lifetime depends entirely on how often the underlying page actually changes and how fresh the result needs to be. A support tool showing "what does this page look like right now" probably wants a short TTL (minutes) or none at all. A dashboard widget showing a marketing page's current state can reasonably cache for longer (an hour or more) since that page rarely changes minute to minute. There's no universal right answer — the point is picking one deliberately instead of defaulting to zero caching by not thinking about it.
Where to Put the Cache
For a single service, an in-memory Map (as shown above) works fine and resets on restart, which is usually fine for this use case. For anything running across multiple instances or processes, a shared store (Redis, or a simple database table with a TTL column) keeps the cache effective across the whole fleet instead of each instance maintaining its own blind spot.
This Is Different From Retry Logic
Caching duplicate requests is a separate concern from retrying failed requests, covered in Rate Limits, Retries, and Batching — that post is about what to do when a request fails; this is about not sending an identical request that would have succeeded fine, twice, in a short window. Both patterns are worth having, and they don't overlap: a cache check happens before a request is sent at all, while retry logic only matters once one has already failed.
When Not to Cache
Anything explicitly checking "did this page change since I last looked" — a visual diff or uptime monitor — needs a fresh capture every time by design; caching would defeat the entire point of the check. This pattern is specifically for repeated requests asking the same question the request just answered a moment ago, not for monitoring workflows.
Cutting out duplicate requests that were never going to return a different answer is a small amount of client-side bookkeeping, and it's the kind of fix that pays for itself quietly, request after request, without Snapshot Site needing to know anything changed on your side at all.

