The State of Caching and Interoperability Between Application Types

Table of Contents

1. Summary

The state of caching and interoperability between application types as of 2016. Three patterns dominate asset naming on CDNs: version-pinned paths, content-hash suffixes, and rolling build identifiers.

1.1. Naming conventions

Observable from HTTP Archive trends data:

1.1.1. CDN: version-pinned path

<link rel="stylesheet"
      href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

The version (3.3.5) is part of the path. Cache-friendly: the URL never changes for a given version. Drawback: no automatic invalidation when a patch is released.

1.1.2. Yahoo: content-hash suffix

css/bundle.c60a6d54.css

The hash (c60a6d54) is derived from the file contents. Any change to the CSS produces a new hash, a new URL, and a guaranteed cache miss.

1.1.3. Microsoft: rolling build identifier

https://assets.onestore.ms/cdnfiles/onestorerolling-1607-15000/shell/v3/scss/shell.min.css

The 1607-15000 encodes the build (Windows 10 Anniversary Update era). Hybrid: the build number changes with each release cycle, but the path is not content-addressed.

2. Tools (2016 baseline)

2.1. Rails Asset Pipeline (Sprockets)

Rails Asset Pipeline Guide — Sprockets compiles, concatenates, and fingerprints assets. The application-<hash>.css naming convention introduced fingerprinting to the Rails ecosystem.

2.2. Webpack

Webpack Caching Guide — content-hash output filenames ([name].[contenthash].js). Rails integration arrived later, via the webpacker gem (Rails 5.1, 2017), and has since been retired.

3. Since then

The content hash won. Every mainstream build tool and framework emits name.<hash>.ext by default, and the version-pinned public CDN lost its main argument when browsers partitioned their caches. What held up from 2016 is the underlying rule: put the version in the URL, cache the URL forever, and spend revalidation only on the HTML that points at it. What changed is everything around that rule — the HTTP spec was reissued, the shared cross-site cache disappeared, AppCache gave way to service workers, CDNs became programmable, and a new cache layer appeared in front of LLM inference.

3.1. HTTP caching: RFC 9111

correct · claude-opus-5-5 · 2026-10-09

RFC 9111 (June 2022, STD 98) replaced RFC 7234 as the definition of HTTP caching. The model is the same — freshness from max-age / s-maxage / Expires, validation with ETag and Last-Modified — but the cleanup is real: the Warning header is obsoleted (caches never set it in practice), Pragma is deprecated, the must-understand directive is new, and duplicate or conflicting directives now have defined handling.

The directives that matter for fingerprinted assets came from separate RFCs:

Header / directive Source Use
Cache-Control: immutable RFC 8246 (Sept 2017) Fingerprinted assets: skip revalidation even on reload.
stale-while-revalidate, stale-if-error RFC 5861 (May 2010) Serve stale while a background refresh runs; serve stale when the origin errors.
CDN-Cache-Control RFC 9213 (June 2022) CDN-targeted TTL. A CDN that honours it ignores Cache-Control and Expires for its own storage.
Vary: Accept-Encoding RFC 9110 / 9111 Adds the request encoding to the cache key; CDNs normalise the value so gzip/br variants don't fragment the cache.

The state machine these directives drive — fresh, stale, revalidation, the SWR side branch — is drawn in HTML5 AppCache Expiration Rules.

3.2. Browser cache partitioning

corrected · claude-opus-5-5 · 2026-10-09

Safari partitioned its HTTP cache first (around 2013). Chrome 86 (October 2020) shipped cache partitioning, keying each entry on the top-level site, the current-frame site, and the resource URL; Firefox 85 (January 2021) followed with network partitioning. A library loaded from cdnjs.cloudflare.com on site A now occupies a different cache slot than the same URL on site B.

That ended the shared-CDN argument from 2016. The extra DNS lookup and TLS handshake to a third-party origin no longer buys a cross-site cache hit. For fonts, self-hosting is now strictly better (Wicki, Nov 2020); for large libraries it depends on CDN geography, but "everyone already has it cached" is gone.

3.3. Service workers replaced AppCache

In 2016 the offline cache was still AppCache, already deprecated and widely disliked. Browsers removed it between Chrome 85 (2020) and Safari 15.4 (2022). Service workers plus the Cache API took over, with the strategies — cache-first, network-first, stale-while-revalidate — written in JavaScript rather than declared in a manifest. The details, and the AppCache flaws that forced the change, are in HTML5 AppCache Expiration Rules. The same fresh/stale/revalidate vocabulary later moved into client data libraries (SWR, TanStack Query).

3.4. This site's own headers

wal.sh is a small worked example of the 2016 problem persisting. The host defaulted to long max-age values (30 days on some assets, 48 h on search-index.json), which hid every ClojureScript bundle deploy from returning visitors. The site's .htaccess template now sets Cache-Control: max-age=86400, must-revalidate on .js and .json. The same audit found the ETag inert: mod_deflate appends -gzip on the way out and then fails to match If-None-Match on the way back, so a conditional GET returns 200, not 304. Last-Modified is the only working validator. Content-hashed filenames would make all of this moot; the bundles are not fingerprinted yet.

3.5. Rails asset pipeline lineage

Years Era Tool Pattern
2011–2022 Rails 3.1–6.1 Sprockets application-<digest>.css
2017–2022 Rails 5.1–7.0 Webpacker (retired) Webpack bundles
2021-- Rails 7+ importmap-rails No bundler; native ES modules
2022-- Rails 7+ Propshaft Simpler fingerprinting, no compilation
2024-- Rails 8 Solid Cache Database-backed fragment caching

Turbo Drive (Hotwire) replaced Turbolinks: navigation fetches HTML and swaps the <body> rather than doing a full page load, and Turbo 8 added DOM morphing for page refreshes. Either way, ETag / 304 Not Modified on the HTML matters more for navigation than asset fingerprinting does.

3.6. Edge computing

CDN edge functions move computation to the cache layer. The cache is no longer passive storage — it can transform responses, personalize content, and enforce access control without a round-trip to the origin.

3.6.1. Major platforms

corrected · claude-opus-5-5 · 2026-10-09

Seven platforms define the landscape. They split on runtime: V8 isolates (near-zero cold start, JavaScript/Wasm only) versus containers or Lambda processes (flexible runtime, second-scale cold start).

Platform Announced / GA Runtime CPU limit Memory
Cloudflare Workers Sep 2017 / Mar 2018 V8 isolates 10 ms free; paid 30 s default, 5 min max 128 MB per isolate
Vercel Edge Functions Dec 2022 GA V8 isolates 50 ms CPU / invocation not published
Netlify Edge Functions 2022 Deno (V8 + Rust) 50 ms CPU 512 MB per deploy
AWS CloudFront Functions 2021 JS: ES 5.1 plus selected ES6–12 (runtime 2.0) submillisecond 2 MB
AWS Lambda@Edge 2017 Node.js / Python 30 s Lambda limits (128 MB–10 GB)
Fastly Compute (was Compute@Edge) 2019 beta WebAssembly (WASI) billed per vCPU-ms 128 MB min
Deno Deploy 2021 V8 isolates (Deno runtime) not published 512 MB

Cold-start comparison: CloudFront Functions and Cloudflare Workers start in under a millisecond because isolates share a warm V8 process. Fastly Compute instantiates a fresh Wasm module per request in tens of microseconds. Lambda@Edge requires container provisioning and can take seconds on a cold path.

Cloudflare Workers pricing: free tier 100,000 req/day, 10 ms CPU. Paid ($5/month minimum): 10 M req/month included then $0.30/million; 30 M CPU-ms included then $0.02/million. CloudFront Functions: $0.10/million invocations.

3.6.2. Computation at the edge changes the caching model

Traditional CDN caching is passive: store the response, serve the stored copy, expire on TTL. Edge functions make caching active.

A/B testing. The worker reads a cookie to determine the user's cohort, rewrites the request path to /test or /control, and fetches from the corresponding cached variant. No origin round-trip for the routing decision. New visitors are randomly assigned and the cohort cookie is set on the response.

Geolocation-based content. Cloudflare injects CF-IPCountry and Vercel injects X-Vercel-IP-Country into every request. A worker can serve a different cached variant per country using Vary: X-Vercel-IP-Country. Vercel's CDN respects this and stores separate cache entries per country value.

Authentication at the edge. JWT validation (HMAC or RS256 via SubtleCrypto) runs in the worker before the request reaches the origin. CloudFront Functions can inspect Authorization headers and return a 403 in submillisecond time; the origin is never hit for unauthorized requests.

Response transformation. Workers rewrite HTML (inject banners, swap canonical URLs, modify <head>), inject security headers (Content-Security-Policy, Strict-Transport-Security), or transform JSON before it is placed in the CDN cache. The cache stores the transformed response, not the raw origin response.

Stale-while-revalidate in the worker. On a stale hit, the worker calls waitUntil() to revalidate the origin in the background after returning the stale copy. Vercel natively honours s-maxage=N, stale-while-revalidate=Z in Cache-Control headers from functions. The Vercel-CDN-Cache-Control header allows different TTLs for the Vercel edge, downstream CDNs, and the browser independently — a vendor-specific instance of the RFC 9213 pattern.

3.6.3. Key technical constraints

These constraints are common across V8-isolate platforms and shape what edge functions can and cannot do.

Constraint Detail
No filesystem No server filesystem access. Assets must be bundled or fetched from KV/R2/D1.
No native modules Wasm available; Node.js .node addons are not.
CPU time cap Tens of milliseconds on free tiers covers routing, header manipulation, and SubtleCrypto operations. Not enough for image transcoding or ML inference without Wasm.
Memory cap 128–512 MB. In-memory state is per-isolate and does not persist reliably across requests.
Startup budget On Cloudflare Workers, global scope must complete within 1 second.
Date.now() does not advance On Cloudflare, Date.now() returns the time of the last I/O event. Timing-sensitive logic must account for this.
No eval() Dynamic code evaluation blocked on all V8-isolate runtimes.
Request body access CloudFront Functions cannot read the request body. Lambda@Edge can.

These constraints explain the CloudFront split: CloudFront Functions for header and URL manipulation, Lambda@Edge for network calls, file access, or heavier compute.

3.6.4. The edge database trend

corrected · claude-opus-5-5 · 2026-10-09

Edge functions became substantially more capable when database primitives moved to the same network tier.

Product Type Launched Notes
Cloudflare KV Key-value 2018 beta Eventually consistent; global replication. Free: 100K reads/day, 1K writes/day, 1 GB. Paid: 10 M reads/month, $0.50/million additional.
Cloudflare D1 SQLite SQL Sep 2023 open beta Strong consistency per database. Free: 5 M rows read/day, 100 K rows written/day, 5 GB. Paid includes 25 B rows read and 50 M rows written per month.
Cloudflare R2 Object storage 2022 S3-compatible; no egress fees. $0.015/GB-month.
Vercel KV Redis-compatible 2023; discontinued Dec 2024 Migrated to Upstash. New projects use Marketplace Redis integrations.
Turso (libSQL) SQLite fork 2023 libSQL is an open-contribution fork of SQLite's C code base, adding replication and per-tenant databases. Turso's separate Rust rewrite of SQLite is a later project.
Neon serverless Postgres 2022 Scale-to-zero compute, built-in pooling. Database branching, point-in-time restore.

The KV / SQL split reflects two use cases. KV (Cloudflare KV, Upstash Redis) stores session tokens, feature flags, and rate-limit counters — small values, high read volume, eventual consistency acceptable. SQL at the edge (D1, Turso, Neon) stores relational data close to compute.

Cloudflare KV is eventually consistent: writes propagate to all edge nodes within roughly 60 seconds. D1 offers strong consistency within a single database; global read replicas trade recency for read throughput.

3.6.5. Solid Cache and the Rails 8 connection

Rails 8 (2024) ships Solid Cache as the default fragment cache store, replacing the Redis/Memcached pattern dominant since Rails 4. Solid Cache stores fragments in a SQLite3 database on SSD (storage/production_cache.sqlite3 by default), using FIFO eviction. Configuration: max_age, max_size (e.g., 256 MB), encrypt, databases (sharding across multiple SQLite files).

  Solid Cache (Rails 8) Cloudflare KV Cloudflare D1
Storage medium SQLite on SSD Distributed key-value store SQLite (edge-replicated)
Consistency Strong (single DB) Eventual (~60 s propagation) Strong per DB; eventual for replicas
Location Origin server 300+ edge PoPs globally 300+ edge PoPs globally
Query model SQL (ActiveRecord) key.get / key.put Full SQL
Eviction FIFO, configurable max_size TTL-based Manual or TTL
Encryption Optional (encrypt: true) Not native Not native
Rails integration Native (ActiveSupport::Cache) Via HTTP API or gem Via HTTP API or gem

Solid Cache is appropriate when the cache lives on the same machine as the Rails process — a local SQLite read is sub-millisecond. Cloudflare KV or D1 is appropriate when the cache must be served from an edge location decoupled from any origin process. The patterns are complementary: a Rails app can use Solid Cache for server-side fragment caching and Cloudflare KV for session tokens served at the edge.

3.7. Prompt caching: the newest layer

correct · claude-opus-5-5 · 2026-10-09

LLM providers now cache the attention state of a prompt prefix and bill reuse at a fraction of the input price. The interop problem from 2016 reappears in a new medium: the cache key is the exact byte sequence of the prefix, so anything volatile placed early — a timestamp, an unsorted JSON blob, a reordered tool list — is the equivalent of a cache-busting query string appended to every request.

Anthropic's prompt caching is explicit: up to four cache_control breakpoints, the cached prefix rendered in the order tools → system → messages, a 5-minute TTL refreshed on each hit (or a 1-hour option). Writes cost 1.25x base input (2x for the 1-hour TTL); reads cost 0.1x on most models and less on the newest. Prefixes below a model-dependent minimum (512–4,096 tokens) are not cached at all. OpenAI's version is on by default, also prefix-based, with discounted cached-token reads.

The design rule is the asset-pipeline rule restated: stable content first and immutable, volatile content last. For agents the stable prefix is the instruction hierarchy (CLAUDE.md, tool schemas); see Agentic Workflow 2026 for how that hierarchy maps onto cache layers, and LLM Cost Monitoring for cache-read versus cache-write pricing in practice.