Dev.to WebDev 🛠 Dev 👁 0 📖 6 min read

Three Cache Tiers on app.onepassapps.com: Which URLs Can Revalidate and Which Cannot

I work on OnePassApps, so this is our own site, measured from outside the browser today - plain HTTPS requests, no session, no cookies, no JavaScript. We have written about what app.onepassapps.com sends back before: th

I work on OnePassApps, so this is our own site, measured from outside the browser today - plain HTTPS requests, no session, no cookies, no JavaScript.

We have written about what app.onepassapps.com sends back before: the response headers, the TLS layer, the sitemap, the llms.txt file. This one is about a narrower question that sits underneath all of those: which of our URLs can a client cache, and which ones can a client ask "has this changed?" and get a straight answer?

I fetched every URL in our own sitemap.xml - 56 of them - and recorded the cache headers on each, in two separate passes. Then I sent a conditional request to five of those pages, and to the three plain-text files a crawler reads and to one hashed asset, each time passing back the ETag the server had just handed me. The answer is that our site is not one cache tier. It is three, and they behave differently from each other.

Tier one: the 56 pages - no validator, so no 304

Every one of the 56 URLs in the sitemap answers 200 and carries the same three cache-relevant headers:

  • cache-control: public, max-age=0, must-revalidate
  • cf-cache-status: DYNAMIC
  • no ETag and no Last-Modified

All 56 of them. Not one page has a validator.

That combination is worth spelling out, because the two halves do different jobs. max-age=0 says the copy may be stored but is immediately stale, and must-revalidate says a stored copy may not be used without asking the server first. So the browser is allowed to keep the page and is required to check back. cf-cache-status: DYNAMIC says the edge in front of us is not serving these from its own cache either - every one of the 56 requests went to the origin.

But the instruction to revalidate has nothing to revalidate against. With no ETag and no Last-Modified there is no fingerprint of the content to compare, so the conditional request cannot succeed. I sampled five of the pages - the home page, two app pages, a studio page and a guide page - and none of them carries a validator to send back. On /apps/newsflow I sent both If-None-Match (with a value that matches no real ETag) and If-Modified-Since (with a date), and it came back 200 with the full document again: 18,765 bytes uncompressed, 5,380 bytes when the client accepts gzip. It comes back in full, because there is no 304 to be had.

That is not a criticism of the pages themselves; it is simply what a site that serves every page fresh looks like from the outside. It does mean a returning visitor re-downloads the document rather than getting a cheap "nothing changed" answer.

Tier two: the three text files - validators, and a 304

The files a crawler reads are served by the same origin but behave differently. Each one carries an ETag, and each one answers a conditional request with a real 304 Not Modified:

File Bytes cache-control ETag Conditional request
/robots.txt 160 public, max-age=14400, must-revalidate yes 304
/llms.txt 17965 public, max-age=0, must-revalidate yes 304
/sitemap.xml 4046 public, max-age=0, must-revalidate yes 304

I sent each file's own ETag back as If-None-Match and got 304 on all three, with no body.

/robots.txt is the odd one out in a useful way: it is the only file of the three that carries a freshness window longer than zero - max-age=14400 is four hours - and the only one whose cf-cache-status reads REVALIDATED instead of DYNAMIC. So the edge does hold that one and re-checks it against the origin, while /llms.txt and /sitemap.xml pass through to the origin every time even though they can be revalidated.

The file sizes are what you would expect for their job: our robots file is 160 bytes, the sitemap 4046 bytes, and llms.txt 17965 bytes carrying the 22 live apps, the guides, the studios and the access-and-cost notes.

Tier three: the hashed asset - one year, served from the edge

The build file is the third tier and the odd one out in the best way. /assets/index-CKM47TUG.js answers with:

  • cache-control: public, max-age=31536000, immutable - one year
  • ETag present, and a conditional request gets 304
  • cf-cache-status: HIT - served from the edge cache, not from the origin

One year is safe here for the ordinary reason: the file name carries a content hash. When the bytes change, the name changes with them, so a URL that is a year old is still pointing at exactly the file it pointed at on the day it was cached. immutable tells the client not to bother revalidating, and HIT shows the edge serving it without asking the origin.

So the same site, at the same moment, answers a request for a page with "here it is again, all 18,765 bytes", for a text file with "unchanged, 304, no body", and for a hashed script with "here it is, from the edge, for a year".

The fourth answer: what a URL that does not exist gets

A URL that is not there is not cached at all. Every miss I tried - a plain path, an /apps/ path, a /guides/ path and a /studios/ path - answered 404 with cache-control: no-store, no ETag, and the same 13,953-byte not-found document. no-store is the strongest instruction in HTTP: nobody may keep a copy, not the browser, not the edge, not an intermediary.

The four misses are the same length but not the same bytes: diffing two of them, the only difference is Cloudflare's per-request encoding of the same support email address. So they are one document rendered per request, not one cached blob served four times.

Why the difference is worth knowing

If you build something against this site, the tier tells you what you are allowed to expect:

  • A page URL always costs a full fetch. If you fetch 22 app pages on every run, you are downloading 22 documents every time. Use llms.txt or the sitemap if you want the list instead of the pages.
  • A text file URL is cheap to re-check. Sending the ETag you already have gets you a 304 with no body, so polling robots.txt costs almost nothing.
  • An asset URL is cheap in a different way: it is served from the edge and stays valid for a year under the same name, so you can pin it and stop thinking about it.

None of this is a scoreboard. no-store on a 404 and max-age=0 on a page are correct choices for pages that change; a validator on the text files and a hashed, immutable year on the asset are correct choices for files that do not. The only thing worth saying out loud is that the page tier cannot answer "has this changed?" even when a client asks nicely - and that is a property of the headers, not of the client.

What I did not test

  • One build, one network, one day: 7 November 2026. Every number came from plain HTTPS requests from outside the browser, with no signed-in session.
  • The 56-URL sweep covers the sitemap only. I did not sweep every URL the site can serve, and I did not check the API host's caching headers.
  • I sent one conditional request per file and per sampled page, not a series, so I am not claiming what a second request in the same second would do.
  • cf-cache-status describes what the edge did on that request from that vantage point. It is not a statement about the edge as a whole, and it can differ between locations.
  • I did not measure how long any browser actually kept a copy, or what the service worker in the app shell does with these headers.

Try the site itself: app.onepassapps.com. The task-by-task guides, including what each tool says it cannot do: the app guides.

Disclosure: I work on OnePassApps.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.