A CDN edge cache is a shared cache in a content delivery network's data centres (points of presence) that stores copies of responses close to users, so repeat requests are answered at the edge instead of travelling to the origin server.
For images such as screenshots and social cards, the edge cache decides how fast they load around the world and how long an old version keeps appearing after you change it. This entry is part of the screenshot glossary.
What it means in plain terms
Your origin server lives in one place. Your visitors do not. A CDN puts many small caches between them. The first visitor near Toronto makes the Toronto edge fetch the image from the origin (a miss). Every later visitor near Toronto gets the stored copy (a hit) until it expires. A visitor near Frankfurt starts the same process at the Frankfurt edge.
visitor (Toronto) ──▶ edge YYZ ──miss──▶ origin (server or storage bucket) │ ◀── image + Cache-Control │ stores copy, Age starts at 0 next visitor (Toronto) ─┘ hit: served from YYZ, Age = seconds since stored visitor (Frankfurt) ──▶ edge FRA ──miss──▶ origin (each edge fills separately)
How it works technically
An edge cache works like any HTTP cache, with a few specific behaviours:
- What it stores depends on the origin's Cache-Control header:
s-maxageif present, elsemax-age, withprivateandno-storeresponses skipped. Many CDNs also cache only certain file types by default, such as images, and treat HTML as dynamic. - The key is normally the URL, sometimes plus selected headers. Two URLs that differ only in a query parameter are two entries.
- The Age header tells you how long the copy has been at the edge. Browsers subtract it from their own TTL.
- Status headers report hit or miss. Names vary by vendor:
cf-cache-status(Cloudflare),x-cache(CloudFront, Fastly and others) and the standardCache-Statusheader from RFC 9211. - Purges are per CDN. Deleting the file at the origin does not remove the edge copies; you purge through the CDN's API or wait for the TTL.
How to inspect it
On 2026-10-15, https://example.com and its script both came back with server: cloudflare, cf-cache-status: HIT and an Age above 7,000 seconds, even though neither sent a Cache-Control header.
Puppeteer: which page resources came from an edge
import puppeteer from 'puppeteer'; const EDGE_HEADERS = ['cache-status', 'cf-cache-status', 'x-cache', 'x-vercel-cache', 'age', 'server']; const url = process.argv[2] ?? 'https://example.com/'; const browser = await puppeteer.launch(); try { const page = await browser.newPage(); const rows = []; page.on('response', (res) => { const h = res.headers(); const row = { url: res.url().slice(0, 60), fromBrowserCache: res.fromCache() }; for (const name of EDGE_HEADERS) if (h[name]) row[name] = h[name]; rows.push(row); }); await page.goto(url, { waitUntil: 'networkidle0' }); console.table(rows); } finally { await browser.close(); }
fromBrowserCache separates the browser's own cache from the edge. In a fresh browser it is false for everything, so any HIT you see came from the CDN.
Playwright: fetch twice and compare
Playwright's API client makes plain HTTP requests without rendering a page. Both requests printed { status: 'HIT', age: '8156', pop: 'YYZ' }. Note that the cache-control: no-cache request header did not force a miss; the edge ignored it:
import { request } from 'playwright'; const url = process.argv[2] ?? 'https://example.com/'; const api = await request.newContext(); try { for (const attempt of [1, 2]) { const res = await api.get(url, { headers: { 'cache-control': 'no-cache' } }); if (!res.ok()) throw new Error(`${url}: ${res.status()}`); const h = res.headers(); console.log(attempt, { status: h['cf-cache-status'] ?? h['x-cache'] ?? h['cache-status'] ?? 'unknown', age: h['age'] ?? '0', pop: h['cf-ray']?.split('-')[1] ?? h['x-served-by'] ?? '-', }); } } finally { await api.dispose(); }
For a quick check without Node:
curl -sI https://example.com/ | grep -iE '^(cache-control|age|cf-cache-status|x-cache|cache-status):'
Tested on 2026-10-15 with Node 22.18.0, [email protected] (run with headless: 'shell'), [email protected] and curl.
RenderScreenshot
When we designed delivery, we kept image bytes away from the API servers. The Cloudflare Worker that renders a page writes the image straight to an R2 storage bucket, and cdn.renderscreenshot.com is that bucket on a custom domain served through Cloudflare. The API server handles only metadata. Every successful response carries the public link in X-Cache-URL:
curl -s -D - -o /dev/null -X POST https://api.renderscreenshot.com/v1/screenshot \ -H "Authorization: Bearer $RS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com", "preset": "og_card", "cache": {"ttl": 604800}}' \ | grep -i '^x-cache'
Use the X-Cache-URL value directly in <img> or og:image tags; it needs no key. It stays available until X-Cache-Expires, which cache.ttl controls. Each fresh render gets a new link and therefore a new edge cache entry, so an old copy at the edge is never served in place of a new render. The caching reference covers TTLs and purges. This request was not run for this page; run the curl check above on the returned link to see its edge headers.
Common mistakes
- Caching per-user images at the edge. A dashboard screenshot served with
publiccan reach the wrong user. Mark itprivateor keep it off the CDN. - Purging the origin and not the edge. The edge keeps serving its copy.
- Testing from one location. A hit in your region says nothing about other edges. Check from where your users are.
- Expecting
no-cachein the request to bypass the edge. Many CDNs ignore it. Purge or change the URL. - Unbounded query strings. Random parameters create one edge entry each and lower the hit rate. Normalize them, as with any cache key.
Related terms
- Cache-Control header: the rules the edge follows.
- Cache TTL: how long the edge keeps a copy.
- Cache invalidation: removing edge copies early.
- S3-compatible storage: the kind of origin behind many image CDNs, including ours.
To get screenshots served from the edge without running your own storage, create a free account and use the X-Cache-URL link.