A cache TTL (time to live) is the length of time a cache may reuse a stored copy without asking the source again; when the TTL runs out, the copy is stale and must be fetched or revalidated.
In HTTP the TTL is usually max-age in the Cache-Control header. In a screenshot service it is how long a render stays reusable and, often, how long its public link keeps working. This entry is part of the screenshot glossary.
What it means in plain terms
A TTL is a "best before" date. Before it, the cache serves its copy without a second thought. After it, the cache has to check. A short TTL means fresher images and more work. A long TTL means less work and a longer window in which a changed page still shows its old screenshot.
rendered TTL = 6 h expired │◀──────────── fresh: reuse, no work ────────────▶│◀── stale: re-render or revalidate 0h age 2h 6h (Age header: 7200, 4 h of life left)
How it works technically
Every cache tracks two numbers: the TTL it was given and the age of the copy. Remaining life is TTL minus age. HTTP passes the age along in the Age header, so a copy that sat on a CDN for two hours arrives at the browser with two hours already used.
There can be two TTLs in one response. max-age is for browsers; s-maxage is for shared caches such as CDNs. We measured this on 2026-10-15 with the Playwright script below on https://www.wikipedia.org/:
| Response | Cache-Control | Age (s) | Browser life left (s) | CDN life left (s) |
|---|---|---|---|---|
| HTML page | s-maxage=86400, must-revalidate, max-age=3600 |
62,461 | −58,861 | 23,939 |
| Logo PNG | s-maxage=86400, max-age=86400 |
62,982 | 23,418 | 23,418 |
The HTML was still fresh for the CDN but already stale for a browser, which must revalidate before using it. Ages and remaining times change on every run. https://example.com sent no Cache-Control at all, only an Age, so its TTL is up to each cache's heuristics.
Choosing a TTL for screenshots
| Use | Suggested TTL | Why |
|---|---|---|
| OG image of a published post | 7 to 30 days | Content rarely changes; version the URL when it does |
| Product or pricing page preview | 1 day | Prices change, but not hourly |
| Link preview for user-submitted URLs | 1 to 7 days | Many repeats of popular links; staleness is minor |
| Dashboard or report image | 1 hour or less | Data changes; old numbers mislead |
| Uptime or visual monitoring | Shortest possible, or no reuse | The point is to see the page now |
| Private screenshot behind login | Short, and purge after download | Limits how long a public link exists |
These are starting points from how the inputs change, not measured optimums. Pair long TTLs with cache invalidation by URL versioning.
How to set it
Puppeteer: a TTL for your own screenshot store
Headless browsers apply HTTP TTLs to the page's own assets. The TTL of the screenshots you produce is your code's job. This version uses the file's modification time. The first run printed missing, rendered; the second fresh (0 s old), expires in 21600 s:
import puppeteer from 'puppeteer'; import { stat } from 'node:fs/promises'; const TTL_SECONDS = 6 * 60 * 60; // 6 hours const url = 'https://example.com/'; const file = 'ttl-example.png'; const info = await stat(file).catch(() => null); const ageSeconds = info ? (Date.now() - info.mtimeMs) / 1000 : Infinity; if (ageSeconds < TTL_SECONDS) { console.log(`fresh (${Math.round(ageSeconds)} s old), expires in ${Math.round(TTL_SECONDS - ageSeconds)} s`); } else { const browser = await puppeteer.launch(); try { const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle0' }); await page.screenshot({ path: file }); console.log(info ? 'expired, rendered again' : 'missing, rendered'); } finally { await browser.close(); } }
Playwright: read the TTLs a page declares
This produced the table above. Run it on your own site to see what browsers and CDNs are told:
import { chromium } from 'playwright'; function directive(cacheControl, name) { const m = new RegExp(`(?:^|,)\\s*${name}=(\\d+)`, 'i').exec(cacheControl); return m ? Number(m[1]) : null; } function ttlReport(headers) { const cc = headers['cache-control'] ?? ''; const age = Number(headers['age'] ?? 0); const browserTtl = directive(cc, 'max-age'); // private caches (the browser) const sharedTtl = directive(cc, 's-maxage') ?? browserTtl; // CDNs and proxies return { cacheControl: cc || '-', age, browserLeft: browserTtl === null ? null : browserTtl - age, sharedLeft: sharedTtl === null ? null : sharedTtl - age, }; } const browser = await chromium.launch(); try { const page = await browser.newPage(); const rows = []; page.on('response', async (res) => { rows.push({ url: res.url().slice(0, 60), ...ttlReport(await res.allHeaders()) }); }); await page.goto(process.argv[2] ?? 'https://example.com/', { waitUntil: 'networkidle' }); console.table(rows); } finally { await browser.close(); }
Tested on 2026-10-15 with Node 22.18.0, [email protected] (run with headless: 'shell') and [email protected].
RenderScreenshot
Set the TTL in seconds with cache.ttl (POST) or cache_ttl (GET), from 3,600 (1 hour) to 2,592,000 (30 days); the default is 86,400 (24 hours). In the Node.js SDK it is .cacheTtl(seconds):
import { Client, TakeOptions } from 'renderscreenshot'; const client = new Client(process.env.RS_API_KEY); const image = await client.take( TakeOptions.url('https://example.com/blog/launch').preset('og_card').cacheTtl(7 * 24 * 60 * 60) // 7 days );
The TTL does two jobs, as the caching reference explains. It sets X-Cache-Expires, how long the public X-Cache-URL copy is kept. And when you send cache.available, it is the maximum age of an earlier render this request will accept, decided by the TTL in the current request. This snippet was not run for this page.
Common mistakes
- Minutes or milliseconds instead of seconds.
ttl: 60is below the API minimum and fails withinvalid_cache_ttl;ttl: 86400000is far above the maximum. - One TTL for everything. A monitoring capture and a blog card have opposite needs.
- Forgetting downstream TTLs. A CDN or social platform may keep the image longer than your TTL.
- Linking to a copy after it expires. Store the image in your own bucket if a link must outlive the TTL.
- Reading
max-agealone. SubtractAge, and checks-maxagefor CDNs.
Related terms
- Cache-Control header: where HTTP TTLs are declared.
- Cache key: which entry a TTL applies to.
- Cache invalidation: ending a TTL early.
- CDN edge cache: the cache that applies
s-maxage.
To pick a TTL per request and reuse renders inside it for 0 credits, get a free API key.