How to Choose stale-while-revalidate Windows for HTML and APIs

This guide answers the tuning question behind Stale-While-Revalidate Implementation, part of Advanced Caching Strategies & CDN Architecture. The stale-while-revalidate=N directive lets a cache serve a response for up to N seconds after it expires, while it fetches a fresh copy in the background. stale-if-error=N lets it serve a stale copy for up to N seconds when the origin is failing. Together with max-age/s-maxage they define three windows — fresh, stale-but-usable, and emergency — and their sizes determine both performance and how old content can get.

There is no universal value. The right windows depend on how often the content changes, how much staleness is acceptable to users and the business, how often each cached object is requested, and whether you can purge on change. A news homepage, a product price API, a documentation page and a configuration endpoint each need different numbers.

The three caching windows for one response Timeline showing the fresh window, the stale-while-revalidate window and the stale-if-error window after a response is cached. The three caching windows for one response Windows fresh stale-while-revalidate if-error only 0min 15min 30min 45min 60min 75min 90min Requests in the SWR window get an instant response and trigger one background refresh; after it, only errors justify serving stale.

Rapid Diagnosis

  • Measure request frequency per object. An object requested every few seconds stays fresh with short windows; one requested every few minutes needs a long SWR window to ever be served stale instead of missing.
  • Measure change frequency. How often does the content actually change? Most pages change far less often than their TTLs assume.
  • Ask the business about staleness tolerance. Is a price 5 minutes old acceptable? A headline? A stock count?
  • Check whether purging exists. With reliable purges, windows can be generous; without them, windows bound staleness.

Root Cause Analysis: Why Defaults Fail

1. Short TTLs without SWR. Every expiry produces a blocking miss for the next visitor; low-traffic objects almost always miss.

2. Huge SWR without purging. Content can be served long after it changed, because a background refresh only happens when a request arrives.

3. No stale-if-error. An origin outage turns cached content into errors as soon as TTLs expire.

4. One policy for everything. Applying the same windows to HTML, APIs and assets ignores their different change rates.

Suggested windows by content type Starting values for s-maxage, stale-while-revalidate and stale-if-error for different kinds of content, assuming purge on change where noted. Suggested windows by content type Content s-maxage stale-while-revalidate stale-if-error Article / docs HTML (purged) 1-24 h 1 day 7 days Homepage / listings 1-5 min 10-60 min 1 day Price / stock API 10-60 s 30-120 s 5-15 min Config / feature flags 30-60 s 5 min 1 day

Step-by-Step: Sizing the Windows

1. Start from staleness tolerance

The maximum age a user can see is roughly s-maxage + stale-while-revalidate (plus propagation of any background refresh). Choose that sum from the business tolerance, then split it: a short fresh window, a longer SWR window.

2. Check traffic per object against the windows

If an object is requested less often than once per s-maxage + stale-while-revalidate, it will usually miss. Either lengthen the SWR window or add tiered caching so requests aggregate.

sql
-- Requests per object per hour, to compare with your windows (CDN logs).
SELECT cache_key, COUNT(*) / 24 AS req_per_hour
FROM cdn_logs WHERE ts > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) AND content_type LIKE 'text/html%'
GROUP BY 1 ORDER BY req_per_hour;
-- trade-off: this ignores how traffic is spread across edge locations. With many
-- PoPs and no tiering, per-location rates are much lower than these totals.

3. Purge on change so windows can be long

With tag- or URL-based purging on publish, staleness is no longer bounded by the windows, so you can set long SWR windows and get near-100% hit rates.

4. Always add stale-if-error

http
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=3600, stale-if-error=86400

A day of stale-if-error keeps the site up through most origin incidents. (No comments are possible in headers; the trade-off is that during an outage users may see content up to a day old — usually far better than an error page.)

HTML hit ratio for low-traffic articles by window Bar chart of edge hit ratio for low-traffic article pages as stale-while-revalidate windows are lengthened. HTML hit ratio for low-traffic articles by window s-maxage=60, no SWR 23% + SWR 10 min 51% + SWR 1 day (purged on edit) 89%

Verification

After changing windows, monitor hit ratio and the Age distribution of served responses: Age should rarely exceed your tolerance for content without purges. Test stale-if-error by temporarily blocking the origin in staging and confirming cached pages still serve. In RUM, TTFB p75 should improve most for low-traffic pages.

Worked Example: Tuning a News Site

A news site used s-maxage=60 with no SWR on all HTML. Breaking news pages were fine — requested constantly — but the long tail of older articles missed 80% of the time, with TTFB p75 of 700ms for those visits. The team kept a 60-second fresh window for the homepage with a 10-minute SWR window, set articles to s-maxage=3600, stale-while-revalidate=86400 with purge-on-edit, and added stale-if-error=86400 everywhere. Article hit ratio rose to 91%, TTFB for rarely read articles p75 dropped to 90ms, and during a 40-minute origin outage the following month, readers saw cached pages instead of errors.

Browser-Side SWR

Browsers also implement stale-while-revalidate in Cache-Control for their HTTP cache. For assets with stable names and APIs called repeatedly, a small browser SWR window (max-age=60, stale-while-revalidate=300) lets repeat requests resolve instantly while refreshing in the background. Use it cautiously for HTML: browser caches cannot be purged, so the browser window directly bounds how stale a page can appear after a deploy. For HTML, keep browser SWR off and rely on the CDN's windows.

Common Mistakes

  • Very long windows without purges. Edits may take hours or days to appear.
  • Forgetting that SWR needs a request to trigger. Content not requested during the window is never refreshed; the next visitor after the window gets a full miss.
  • Identical windows for volatile APIs. Prices and stock need short windows or event-driven purges.
  • Expecting SWR to fix uncacheable responses. Private or cookie-setting responses are not cached at all.

Edge Cases

CDN support. Most major CDNs honour stale-while-revalidate and stale-if-error; some use vendor-specific equivalents (for example, Fastly's surrogate controls). Check your CDN's behaviour.

Concurrent revalidation. Good CDNs send one background request per object; poor implementations may send many under load.

Service workers. Workbox's StaleWhileRevalidate strategy is a separate, client-side implementation; align its behaviour with your headers.

Errors during revalidation. If the background refresh fails, the cache keeps serving stale (within the SWR window) and retries on the next request.

FAQ

Does stale-while-revalidate delay updates after a purge?

No. A purge removes the entry; the next request is a miss that fetches fresh content. SWR only affects entries that expired naturally.

Is there a maximum useful SWR window?

For purged content, windows of a day or more are common. Beyond that, gains are small because most objects are refreshed by traffic or purges sooner, and eviction policies may remove rarely used entries anyway.

Should APIs and HTML share a policy?

Rarely. APIs often change more frequently and are consumed by code that can handle refreshes; HTML is consumed by people and benefits from longer windows plus purges.

How do I measure staleness actually served?

Log the Age header (or compute content age from timestamps embedded in responses) at the edge and chart its distribution per content type.

What about stale-if-error for personalised content?

It only applies to cached responses; personalised responses that are not cached cannot be served stale. For resilience there, use fallbacks in the application.

Does SWR improve LCP or only TTFB?

It improves TTFB directly, and LCP for server-rendered pages in proportion, because more requests are served from cache instead of waiting for the origin.

/html>