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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Cache a screenshot by hashing the target URL together with every rendering input, then apply a TTL that matches how quickly the page changes. Keep a durable copy in your own object storage when you need retention, use private or no-store responses for personalized images, and provide an explicit fresh-capture path that bypasses both your cache and the provider cache.

What a correct screenshot cache must do

A screenshot is the output of a rendering request, not merely a URL. The same page can produce different pixels when any of these inputs changes:

  • Viewport width and height, device preset, device scale (retina), orientation, or full-page mode
  • Output format, quality, paper size, margins, landscape mode, or PDF page range
  • Locale, timezone, geolocation, user agent, custom headers, cookies, or Authorization
  • Injected CSS or JavaScript, clicked elements, hidden selectors, selected CSS element, or transparent-background settings
  • Wait-for-selector rules, fixed delays, network-idle rules, blocked requests, resource types, ads, trackers, and cache settings

Build a canonical representation of all pixel-changing values, hash it, and use that hash as the object and HTTP cache key. ScreenshotEngine explicitly notes that changing capture options creates a different cache key; do not key only on the page URL. Also do not assume GET and POST requests share a provider cache entry.

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

A practical cache-key format

tenant/{tenantId}/sha256({
  "url": "https://example.com/pricing",
  "format": "webp",
  "viewport": {"width": 1440, "height": 900},
  "deviceScale": 2,
  "locale": "en-US",
  "timezone": "UTC",
  "authContext": "customer-42",
  "css": "",
  "javascript": "",
  "selector": null,
  "wait": {"networkIdle": true, "delayMs": 0}
})

Canonicalize the URL (for example, sort query parameters only when your application knows order is irrelevant), serialize JSON with stable key ordering, and hash the resulting bytes. Include a tenant and authorization context for private captures. Never put an access token, cookie value, or other secret directly in a public URL or shared cache key; use a one-way identifier for the authorization context instead.

Provider cache versus your durable cache

Screenshot providers often cache rendered results to avoid repeating browser work. Treat that layer as an optimization, not permanent storage. ScreenshotEngine documents a 24-hour capture-cache lifetime that can end earlier when an instance restarts, and advises saving returned files in your own storage when permanent access is required. It also states that successful requests, including cache hits, count toward monthly usage.

Screenshot API documents cache=true, a cacheTTL in seconds with an 86,400-second default, and staleTTL for serving stale content while a refresh runs. ScreenshotOne documents a four-hour default and cache_ttl values up to one month. Those are vendor-specific settings, not universal recommendations. Your application should still write a successful response to object storage or a persistent blob store when retention, auditability, or high read volume matters.

Recommended request flow

  1. Normalize the URL and canonicalize every rendering option.
  2. Hash the canonical request, including tenant and authorization context when applicable.
  3. Read your durable/object cache first if you need retention beyond the provider’s cache.
  4. On a miss, call the screenshot API with its documented cache flag and chosen TTL.
  5. Store the bytes with the correct Content-Type, byte length, an immutable or versioned path, and an ETag where possible.
  6. Return an HTTP Cache-Control policy that matches privacy and freshness requirements.
  7. For an explicit refresh, bypass provider lookup and replace the versioned object only after a successful render.

Choosing a TTL

TTL is a product decision. Balance page-change frequency, acceptable visual staleness, rendering cost, privacy, invalidation complexity, and storage cost.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Page type Starting policy Reason
Breaking news or rapidly changing dashboard Minutes Visual information becomes stale quickly; use a short provider and CDN TTL.
Marketing landing page Hours Changes are less frequent, while repeated renders can be expensive.
Stable documentation A day or longer Long-lived versions reduce browser work and storage churn.
Personalized or authenticated page Private cache or no-store Privacy is more important than reuse; never expose one user’s image to another.

A provider’s default is only a baseline. For example, Screenshot API’s 86,400-second default and ScreenshotOne’s four-hour default describe those services, not a guarantee that a day or four hours is correct for your page.

Stale-while-revalidate

When a slightly old image is acceptable, serve the existing object immediately and refresh it in the background. A provider’s staleTTL can support this behavior, but your own cache should also record the object’s creation time and refresh status. Prevent stampedes with a per-key lock or single-flight mechanism so hundreds of simultaneous misses do not launch hundreds of browser jobs.

Putting a screenshot endpoint behind a CDN

For a public image endpoint, return a stable URL backed by your origin and configure CDN caching. Set an immutable, versioned path when the content should never change, or use a short s-maxage when the path is stable and the image is replaced.

Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=60
ETag: "sha256-RENDERED-BYTES"
Content-Type: image/webp
Content-Length: 184392

Cloud CDN documentation lists common reasons a response is not shared: Set-Cookie, Cache-Control: no-store or private, a request containing no-store, an unsuitable Vary header, and many authenticated requests. Remove accidental cookies from public responses and keep authorization-specific captures on a private distribution or direct origin.

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

Validators and conditional requests

Use ETag values derived from rendered bytes or a version hash. Clients and CDNs can send If-None-Match; return 304 Not Modified when the object is unchanged. Google Media CDN requires Last-Modified or ETag, plus valid Date and Content-Length, before origin responses larger than 1 MiB are cached. Supplying those headers consistently also makes debugging and revalidation easier.

Forcing a fresh screenshot

Expose a deliberate refresh operation instead of asking callers to add random query strings. Authenticate the operation, bypass your durable cache, and request a provider bypass option. ScreenshotEngine supports a POST cachePolicy: "no-cache" that bypasses both lookup and storage; it reports X-Cache: HIT, MISS, or BYPASS. For other services, use the documented fresh-capture or disable-cache parameter. If a provider has no such option, add a version component to your own key and keep the old object until the new render succeeds.

// Pseudocode for an explicit refresh
const key = canonicalHash(request);
const result = await provider.capture({ ...request, cachePolicy: "no-cache" });
if (result.ok) {
  await objectStore.put(`shots/${key}/${result.version}.webp`, result.bytes, {
    contentType: "image/webp",
    etag: sha256(result.bytes)
  });
}

Log the normalized key (without secrets), TTL, cache result, render duration, provider request ID, page version if available, and whether the response was billed. This lets you distinguish a stale hit from a slow origin render.

Privacy and security rules

  • Use Cache-Control: private or no-store for screenshots containing account data, tokens, invoices, internal dashboards, or other confidential information.
  • Partition keys by tenant and authorization context. A URL alone is never sufficient for an authenticated page.
  • Do not include cookies, bearer tokens, or signed provider credentials in a cache key, filename, referrer, or public image URL.
  • Strip Set-Cookie from a response only when you are certain the screenshot is public and no session state is required.
  • Encrypt private objects, restrict bucket access, and expire temporary download links.
  • Review HTML and image metadata for secrets before making a capture public.

Performance, reliability, and cost

Cache hits reduce browser startup and page-load latency, but they may still count as successful usage at the provider. ScreenshotEngine explicitly says cache hits count toward monthly usage, so measure billed requests rather than assuming a hit is free. Keep a small durable metadata record alongside each object: key, source URL, option hash, created time, expiration, byte size, ETag, provider cache result, and billing indicator.

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.

Use bounded concurrency for bulk refreshes, exponential backoff for transient network errors, and a maximum render timeout. Serve the last known good image when policy allows, but label it internally as stale and alert when refresh failures exceed your threshold. Never overwrite a good object with an error page or an empty response.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failure modes and fixes

Every viewport returns the same image

Cause: the key contains only the URL. Fix: include viewport, device scale, format, and every other pixel-changing option before hashing.

Changing CSS does not change the result

Cause: injected CSS or JavaScript was omitted from the canonical request, or a mutable cache entry is being reused. Fix: include normalized script/style content (or a secure content hash) and perform a documented bypass refresh.

A fresh request still returns an old image

Cause: your CDN or browser is serving its own cached object. Fix: use a new versioned path, send an appropriate revalidation request, and confirm origin headers and provider cache telemetry.

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

Private images appear in public cache hits

Cause: authorization context is missing from the key or a shared response was marked public. Fix: partition by tenant, return private or no-store, purge exposed objects, and rotate any credentials that may have appeared in the capture.

CDN refuses to cache large files

Cause: missing validators or required headers. Fix: provide ETag or Last-Modified, valid Date, and accurate Content-Length; Google Media CDN requires these for origin responses over 1 MiB.

Cache usage is higher than expected

Cause: the provider bills successful hits, or harmless option ordering creates different hashes. Fix: check the provider’s billing rule, canonicalize input consistently, and monitor hit, miss, bypass, and billed counters separately.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, with X-Page-Verdict and X-Billed headers explaining the result. Its cache supports a TTL you choose, and you can still store the returned bytes in your own CDN or object storage.

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

One GET request returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo includes an MCP server so Claude, Cursor, and other MCP clients can take screenshots, inspect page information, or capture PDFs. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Should I cache PDFs the same way as images?

Use the same keying and privacy rules, but include PDF-specific values such as paper size, margins, orientation, and page range; those options change the document bytes.

What should I retain for an audit trail?

Keep the canonical option hash, creation and expiration times, object ETag, source URL, tenant identifier, provider cache result, and billing indicator while excluding secrets and raw credentials.

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

Can a cache key contain the full authenticated URL?

Avoid it. Query strings can leak credentials through logs and analytics; use a redacted canonical URL plus a one-way authorization-context identifier.

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.