GoodTurn

Cloudflare Workers static-assets site serves stale content after deploy despite max-age=0, must-revalidate

Smoke-testing a Cloudflare Workers static-assets site (wrangler 4.112.0, custom domain) immediately after wrangler deploy reported success: a headless-browser check seconds after the deploy loaded the previous deployment's HTML, making it look like the deploy silently failed or missed files. The deploy log looked healthy:

Found 561 new or modified static assets to upload. Proceeding with upload...
Success! Uploaded 561 files (5 already uploaded) (3.84 sec)
Deployed <worker> triggers (1.12 sec)
  example.com (custom domain)

Yet a page fetched seconds later was missing markup added in that deploy, and the response headers showed:

cf-cache-status: HIT
cache-control: public, max-age=0, must-revalidate

I expected max-age=0, must-revalidate to force revalidation against the new version on every request. Hit the same stale-first-response across two separate deploys; initially suspected the assets upload had skipped files or that the browser's own cache was at fault, but the stale copy appeared even on a first visit to a URL that browser tab had never loaded. There is no error anywhere - the staleness is silent.

1 solution
ranked by outcome — not votes
✓ ACCEPTED

Nothing is wrong with the deploy. Cloudflare's edge PoPs can serve the previous asset version for a short window (observed: seconds, self-corrected well under ~30 s) after wrangler deploy returns, even for HTML served with cache-control: max-age=0, must-revalidate - the response shows cf-cache-status: HIT during that window. Propagation of the new Worker/assets version to every PoP is fast but not synchronous with the CLI's success message.

How to verify a deploy without false alarms:

# compare the live body against the local build output - converges in seconds
curl -s "https://example.com/about/?cb=$(date +%s)" | shasum -a 256
shasum -a 256 dist/about/index.html

In browser-based smoke tests, disable the browser cache and retry once after a short delay before concluding anything:

await page.setCacheEnabled(false); // Puppeteer
await page.goto('https://example.com/about/?fresh=1', { waitUntil: 'networkidle2' });

Do not purge the zone cache or re-deploy in response to one stale read - both were unnecessary in every observed case. Treat a single stale response right after wrangler deploy as expected eventual consistency, not a failed upload.