Implementing Cache Keys with Query Parameters vs Filenames
Selecting a cache key strategy dictates how edge proxies, CDNs, and reverse proxies store and serve static payloads. Misalignment between your deployment fingerprinting method and proxy normalization rules causes stale asset delivery, cache fragmentation, and deployment rollbacks. This guide provides exact diagnostic workflows, configuration matrices, and incident resolution steps for query parameter versus filename-based cache keys.
Symptom Identification: Stale Assets and Cache Misses
Deployment failures rarely manifest as outright 5xx errors. They surface as HTTP 200 responses serving outdated JavaScript or CSS bundles alongside HTTP 404s for newly generated hashes. Rapid identification requires isolating the proxy layer from the origin.
Execute targeted header inspections to trace cache behavior:
curl -sI -H "Accept-Encoding: gzip" https://cdn.yourdomain.com/bundle.js?v=a1b2c3
Inspect the response headers. Look for X-Cache: HIT, CF-Cache-Status: HIT, or Age: 86400. A HIT on a newly deployed hash confirms proxy normalization is stripping your query string. Conversely, a MISS or BYPASS indicates the cache key is unique but not being populated correctly.
In browser DevTools, open the Network tab, disable cache, and reload. Filter by JS or CSS. Check the Size column. disk cache or memory cache indicates local browser caching, while (from cache) with a 304 status indicates conditional request validation. If the payload size matches the previous deployment despite a new query parameter, the edge proxy is normalizing the key.
| Cache Header Value | Proxy Behavior | Immediate Action |
|---|---|---|
X-Cache: HIT / CF-Cache-Status: HIT |
Query string stripped by default normalization | Override cache key directive or migrate to filename hashing |
X-Cache: MISS |
Key recognized, but origin not cached | Verify Cache-Control headers and origin TTL |
X-Cache: BYPASS |
Explicit bypass rule or dynamic path | Audit proxy_cache_bypass or edge routing rules |
Establish baseline hashing workflows before modifying edge configurations. Review Static Asset Fingerprinting Fundamentals to align your build pipeline with deterministic output generation.
Query Parameter Cache Keys (?v=hash)
Query string fingerprinting appends a version identifier to the asset path: app.js?v=8f3a9c. While simple to implement, it conflicts with default CDN behaviors. Most edge networks ignore query parameters for static MIME types (text/javascript, text/css, image/png) to maximize cache efficiency. This optimization causes persistent stale cache hits when you deploy a new hash.
To enforce query-aware caching, you must explicitly configure the cache key normalization matrix. Without this, the proxy treats app.js?v=1 and app.js?v=2 as identical keys.
This strategy remains viable for legacy systems, rapid A/B testing, or environments where build pipelines cannot rewrite HTML references. However, it introduces normalization overhead and increases debugging complexity during incident response.
# Nginx: Preserve query strings in cache key
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=static_cache:10m max_size=1g inactive=7d;
server {
location ~* \.(js|css|png|jpg|svg)$ {
proxy_cache static_cache;
# Explicitly include $request_uri to capture query parameters
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_pass http://origin_backend;
}
}
When query strings are preserved, cache hit ratios drop if tracking parameters (?utm_source=, ?cb=) are not filtered. Implement strict regex normalization to retain only fingerprint parameters. Reference Cache Key Architecture for detailed normalization matrices and proxy chaining behavior.
Filename-Based Cache Keys (asset.hash.ext)
Filename hashing embeds the content hash directly into the path: app.8f3a9c.js. This approach guarantees cache invalidation upon file modification. The URL itself becomes the cache key, bypassing proxy normalization quirks entirely.
Modern bundlers (Webpack, Vite, esbuild) generate these outputs by default. The strategy aligns with HTTP/2 multiplexing and enables aggressive Cache-Control: immutable directives. Once deployed, the asset never changes. Proxies can cache it indefinitely without validation requests.
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: public, max-age=31536000, immutable
ETag: "8f3a9c"
Implementation requires build pipeline integration. The CI/CD process must:
- Generate content hashes during compilation.
- Emit a manifest mapping logical filenames to hashed outputs.
- Inject hashed paths into HTML templates before deployment.
If the HTML reference update fails, the browser requests the old filename. Since the old file is deleted or renamed, the origin returns 404. Validate manifest injection in staging environments before promoting releases.
CDN & Reverse Proxy Configuration
Edge proxy directives dictate how cache keys are constructed, normalized, and stored. Misconfiguration here causes cache fragmentation or complete bypass.
Nginx: Query String Preservation
proxy_cache_key "$scheme$request_method$host$request_uri";
Ensures every unique query string generates a distinct cache entry. Prevents stale hits when using ?v=hash.
Cloudflare Edge Worker: Explicit Key Normalization
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// Override default query-stripping for static assets
const normalizedKey = `${url.pathname}${url.search}`;
const cache = caches.default;
let response = await cache.match(request);
if (!response) {
response = await fetch(request);
event.waitUntil(cache.put(request, response.clone()));
}
return response;
}
Explicitly concatenates path and query string. Bypasses Cloudflare’s default static asset query normalization.
Varnish VCL: Query String Stripping
sub vcl_recv {
if (req.url ~ "\.(js|css|png|jpg|svg)$") {
# Strip query parameters to force filename-only caching
set req.url = regsub(req.url, "\\?.*$", "");
}
}
Removes query parameters to maximize cache hit ratios. Use only when deploying filename-hashed assets.
Validate origin Cache-Control: immutable headers for filename-hashed assets. Proxies will ignore immutable if max-age is missing or if Vary headers conflict with your cache key strategy.
Edge Case Handling & Validation
Multi-tier caching introduces key drift. An edge node may cache app.js?v=1 while the origin shield caches app.js?v=2 due to differing normalization rules. Test end-to-end propagation using origin bypass headers:
curl -sI -H "Cache-Control: no-cache" https://cdn.yourdomain.com/app.8f3a9c.js
Verify CI/CD pipelines generate identical hashes for unchanged files. Non-deterministic builds (e.g., timestamps in source maps, randomized chunk IDs) cause cache misses on every deployment. Enable contenthash in Webpack or assetFileNames in Vite to guarantee deterministic output.
Implement automated cache purge fallbacks for emergency rollbacks. Do not rely on manual UI purges during incidents. Script CDN API calls to purge by tag or prefix:
# Cloudflare CLI purge by tag
cf zone purge-cache --zone-id $ZONE_ID --tag "release-v2.4.1"
Common Pitfalls & Incident Resolution
| Issue | Root Cause | Resolution |
|---|---|---|
| Stale assets served despite query string update | CDN default configuration ignores query parameters for static MIME types | Explicitly configure cache key normalization to include $request_uri or migrate to filename hashing |
| Cache explosion and 100% miss rate | Dynamic tracking parameters (?utm_source, ?cb=timestamp) included in cache keys |
Implement regex-based query string filtering to retain only fingerprint parameters and strip analytics tags |
| Broken references after deployment | HTML templates not updated to reflect new filename hashes during CI/CD | Integrate asset manifest injection into the build step and verify HTML output before deployment |
| Mixed content warnings on HTTPS | Proxy rewrites http:// to https:// but cache key normalization strips protocol |
Enforce proxy_cache_key "$scheme$request_method$host$request_uri" and validate origin URLs |
Frequently Asked Questions
Do CDNs cache query strings by default? Most CDNs ignore query strings for static assets to maximize cache hits and reduce storage overhead. Explicit configuration is required to treat them as unique cache keys.
Which strategy offers better cache hit ratios?
Filename hashing guarantees 100% cache invalidation on change and avoids proxy normalization quirks. It yields higher long-term hit ratios when paired with Cache-Control: immutable.
Can I mix both strategies in the same deployment? Yes, but it complicates cache key normalization and increases debugging overhead. Standardize on one approach per asset type to prevent cache fragmentation and simplify incident response.