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.
This is the pattern that won — fingerprinting is now the default in
every major framework.
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). The 2015-era integration with Rails via
webpacker gem is now deprecated.
3. 2026 postscript
The landscape changed substantially since 2016.
3.1. 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 (deprecated) | 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) replaces Turbolinks and changes full-page cache
invalidation: the browser fetches HTML and morphs the DOM rather than
doing full page loads. This makes ETag / 304 Not Modified more
important than asset fingerprinting for navigation.
3.2. HTTP caching headers
| Header | Significance |
|---|---|
Cache-Control: immutable |
RFC 8246 (2017). Fingerprinted assets should never be revalidated. |
stale-while-revalidate |
RFC 5861. Serve stale while fetching fresh in background. |
Vary: Accept-Encoding |
Required for CDN cache key normalization with gzip/brotli. |
cdn-cache-control |
CDN-specific TTL, separate from browser Cache-Control. |
3.3. Browser cache partitioning
Safari partitioned third-party caches first (~2013; ITP 2.1 in Safari
12.1, early 2019). Chrome 86 (October 2020, gradual rollout) and
Firefox 85 (January 2021) followed. The HTTP cache is now keyed by a
tuple of (top-level site, frame site), not just the resource URL. A
library loaded from cdnjs.cloudflare.com on site A occupies a
different cache slot than the same URL on site B.
This eliminated the cross-site cache reuse that made shared CDNs a performance win. The extra DNS lookup and TLS handshake to a third-party origin now has no cache benefit to offset it. For assets like fonts, self-hosting removes that overhead entirely (Wicki 2020). For large libraries, the tradeoff depends on CDN geography and server configuration — but the "everyone shares one cached copy" argument is gone.
3.4. 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.4.1. Major platforms
Six platforms define the current 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; 30 s paid | 128 MB |
| Vercel Edge Functions | Dec 2022 GA | V8 isolates (no MicroVM) | 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 ECMAScript 5.1 | submillisecond | 2 MB |
| AWS Lambda@Edge | 2017 | Node.js / Python | 30 s viewer; 30 s origin | 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 achieve submillisecond startup because isolates share a warm V8 heap. Fastly Compute reaches ~50 µs via Wasm module instantiation. 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. Vercel Edge: 500,000 execution units/month free (Hobby); 1 M (Pro); 1 unit = 50 ms CPU. CloudFront Functions: $0.10/million invocations.
3.4.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.
3.4.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 | 10–50 ms covers routing, header manipulation, and SubtleCrypto operations. Not sufficient for image transcoding or ML inference without Wasm. |
| Memory cap | 128–512 MB. In-memory state is per-isolate and does not persist 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.4.4. The edge database trend
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: 25 B rows read/month, 50 M rows written, 5 GB storage. |
| 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 | Rust rewrite of SQLite. Concurrent writes, vector search, per-tenant databases. Wasm-portable. |
| Neon | Serverless Postgres | 2022 | Scale-to-zero with pgBouncer. 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 ~60 seconds. D1 offers strong consistency within a single database file; global read replicas trade recency for read throughput.
3.4.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.