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:

  1. Generate content hashes during compilation.
  2. Emit a manifest mapping logical filenames to hashed outputs.
  3. 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.