Enterprise Cache Refresh & Proxy Fingerprint Logs
The problem
Some users β notably at large institutions like utsa.edu β load a very old
version of pdf.theaccessible.org even days after a deploy. This is almost always
a corporate forward proxy / secure web gateway (SWG) on the userβs side
(Blue Coat / Symantec, Squid, Zscaler, etc.) serving a stale HTML shell from its
own cache and ignoring our Cache-Control: must-revalidate.
Our server-side headers are already correct (verified live):
| Response | Cache-Control |
|---|---|
HTML (/, /pricing/, β¦) | public, max-age=0, must-revalidate + ETag |
Hashed JS/CSS (/_next/static/*) | public, max-age=31536000, immutable |
Once a proxy serves an old HTML shell, it references old (but still-present) hashed chunks, so the whole app is stale. No response header can fully force a non-compliant proxy to revalidate β which is why the fixes below are layered.
What we deployed
1. Client-side version self-heal (the real cure)
- The CF web worker (
apps/web/worker/index.ts) servesGET /__versionβ dynamic,no-store, deployed atomically with the app β returning the live build number. Because the worker generates it (not a static file), a proxy that caches our origin canβt stale it. VersionChecker(apps/web/src/components/VersionChecker.tsx) polls it on mount, on tab focus, and every 5 minutes, comparing to the build number baked into the running bundle (NEXT_PUBLIC_BUILD_NUMBER).- When the running bundle is older, it shows a βRefresh nowβ banner. We prompt rather than force-reload because the app has in-flight uploads and unsaved edits.
- Loop guard: a per-build
sessionStoragemarker (tae:reloadedForBuild) detects when a refresh did not clear the staleness (the proxy re-served the old shell) and escalates the copy to a hard-refresh hint (Ctrl/β+Shift+R).
The build number reaches the worker via wrangler deploy --var (buildVersion.ts
is gitignored, so it canβt be imported into the worker bundle). See
apps/web/scripts/wrangler-version-args.mjs; the deploy npm script wires it in.
2. Legacy proxy headers
apps/web/public/_headers adds Pragma: no-cache + Expires: 0 to the HTML
rules β HTTP/1.0 directives that older appliances honor even when they ignore
must-revalidate. A /*/ rule covers trailing-slash directory routes
(/pricing/, /admin/) that /*.html does not match. Extensioned assets never
end in /, so the immutable hashed-asset rule is untouched.
3. Edge proxy fingerprint logging
For HTML document requests that carry a forward-proxy signature, the worker
logs the network fingerprint to app_logs, so we can identify exactly what
infrastructure a stale user sits behind.
Reading the fingerprint logs
-
Open /admin/logs (admin.theaccessible.org β Logs).
-
Filter for
proxy fingerprint(the log message) orcomponent = browser. Each entryβsmetadatahaskind: "proxy-fingerprint". -
Key fields in
metadata:Field Meaning asOrg/asnCloudflare-resolved network. The biggest tell. University of Texas at San Antonio= on-campus egress; a Zscaler ASN (AS22616 / AS40384 / AS53813) = cloud SWG.cfIpClient IP as Cloudflare saw it. In UTSAβs 129.115.0.0/16= on-campus; elsewhere = proxied off-site.viaOften literally names the appliance, e.g. 1.1 β¦(BlueCoat),1.1 zscaler,squid/β¦.xForwardedFor/forwardedProxy chain. blueCoat/zscalerVendor-specific headers when present. ifNoneMatchabsenton an HTML navigation is a smoking gun β a well-behaved cache echoes our ETag to revalidate; its absence means the proxy is serving from its own cache without checking us.coloCloudflare edge that served the request (geography sanity check).
Interpreting the result
asOrg/cfIp= UTSA,vianames an appliance,ifNoneMatch: absentβ on-campus caching proxy serving stale HTML. The version self-heal + hard- refresh hint is the mitigation; there is no server-side fix.asOrg= a Zscaler/cloud SWG ASN β cloud gateway. These usually cache less aggressively; check whether they strip our headers.- Few or no UTSA entries despite stale-bundle reports β the proxy is caching
our origin so uniformly that even
/__versionrequests donβt reach us. In that worst case the client self-heal canβt fire either, and the hard-refresh hint (or a proxy-side cache purge by their IT) is the only recourse.
Tuning
FINGERPRINT_SAMPLE(wrangler var, default"1"= log all matching requests) throttles volume if the logs get noisy. Set e.g."0.2"to sample 20%,"0"to disable. Override at deploy with--var FINGERPRINT_SAMPLE:0.2or editwrangler.toml.- The ingest endpoint (
/api/client-logs) rate-limits per source IP; since the workerβs egress is a small set of Cloudflare IPs, sustained high volume can be dropped collectively β another reason to sample if traffic is heavy.
Verifying a deploy
# Should return the just-deployed build number, no-store:curl -si https://pdf.theaccessible.org/__version | grep -iE 'cache-control|pragma|buildNumber'curl -s https://pdf.theaccessible.org/__version
# HTML should carry the legacy headers:curl -sI https://pdf.theaccessible.org/pricing/ | grep -iE 'cache-control|pragma|expires'