Most TTLs are copied from a tutorial and never revisited. This guide replaces the folklore with a four-question decision tree that lands every asset class — versioned, static-ish, HTML, API, personal — on a defensible number, and explains the two-layer trick that makes long edge TTLs safe.
The four questions
Every TTL decision reduces to four questions asked in order. Can the content at this URL ever change? If no (content-hashed assets), the answer is effectively infinite and you are done. If it changes, how wrong is stale? Measure in consequences, not squeamishness: a day-old blog post is fine, a stale price is a complaint, a stale API auth decision is an incident. Can you purge on change? Reliable event-driven purging (per the purge guide) lets TTLs run long because freshness comes from events, not expiry. Who else caches this downstream? Browser caches and intermediary caches don't hear your purges, which is why the edge and browser answers differ — section four's whole subject. Four honest answers place every asset in one of the classes below.
Versioned assets: the easy infinity
Content-hashed and version-parameterized assets take max-age=31536000, immutable with no further thought — the URL is the version, change ships as a new URL, and short TTLs here buy zero safety at real revalidation cost. The only work is ensuring the class is honestly delimited: everything in the build pipeline's hashed output qualifies; the un-hashed stragglers (favicon, robots.txt, a legacy embed script) do not, and giving them the immutable treatment because they live in the same folder is the classic error this class produces. Delimit by pattern (/assets/*.[hash].*), not by directory.
Mutable-at-a-URL: the judgment cases
Everything that can change at a stable URL gets TTL = how long you can tolerate staleness, discounted by your purge reliability. With solid event purging: HTML pages run comfortably at hours (purge carries precision; TTL is just the safety net for missed events), product and category pages at 10–60 minutes depending on inventory velocity, shared API reads at the micro-caching seconds-scale from the API guide. Without event purging, divide those numbers by ten and prioritize building the purge hooks — TTL-only freshness forces short TTLs, which forfeits most of caching's value on exactly the expensive-to-render content that needs it. Personal and session-bearing responses are not a TTL class at all: they bypass, per the exclusion patterns every platform guide in this library repeats.
Edge TTL vs browser TTL: the two-layer trick
The edge hears your purges; browsers do not — so the same object can safely carry a long edge TTL and a short browser TTL, and splitting them is the highest-leverage TTL technique available. Use s-maxage for the shared-cache (edge) lifetime and max-age for browsers: Cache-Control: max-age=60, s-maxage=86400 gives the edge a day of hits (revocable by purge) while browsers re-check every minute (cheaply — a revalidation against the edge is a 304 in a few milliseconds). Platforms that support origin-facing TTL overrides achieve the same split from the console. The pattern rescues HTML especially: full-page caching with day-long edge TTLs, minute-long browser TTLs, and purge-on-publish gives near-perfect freshness with near-perfect hit ratios — the combination naive single-number TTLs cannot reach. Add stale-while-revalidate from the serve-stale guide and even the revalidation moments vanish from the latency chart.
The TTL table you should write down
The deliverable is a one-page table in your repo: asset class pattern → edge TTL → browser TTL → purge trigger → owner. Something like: hashed assets / 1y+immutable / 1y / never needed; images & fonts / 30d / 7d / on replacement; HTML / 24h s-maxage / 60s / publish hook; product pages / 1h / 60s / inventory webhook; shared API reads / 15s / no-store / write-path purge; personal & auth / bypass / no-store / n-a. The table ends TTL archaeology (why is this 14400?), gives code review something to check new routes against, and — audited yearly against measured hit ratios — keeps the numbers honest as the product changes. TTLs set once and written down beat TTLs set thoughtfully and remembered by nobody.
