Illustration for Resumable.js Caching operations guide

Caching

Cache-Control headers, CDN caching strategies, and upload endpoint cache avoidance for Resumable.js deployments.

Ops·Updated 2025-10-15

Caching is the single fastest lever you can pull to improve perceived performance for any web application—and the single most dangerous one to misconfigure around upload endpoints. Resumable.js deployments involve two fundamentally different traffic patterns: static assets that should be cached aggressively, and upload API endpoints that must never be cached under any circumstances. This page covers Cache-Control header strategy, CDN behavior, cache busting for assembled files, and the split caching architecture that keeps uploads reliable while static content flies. For the full set of deployment and infrastructure topics, see the ops hub.

Why Upload Endpoints Must Not Be Cached

Picture this: a CDN caches the response to a GET /api/upload?resumableIdentifier=abc123&resumableChunkNumber=3 request. The server said "chunk not found" with a 204. The CDN remembers that response. Now the client uploads chunk 3, but the next GET test request still hits the cached 204. Resumable.js thinks the chunk is missing and uploads it again. And again. Forever.

This isn't theoretical. It happens in production whenever a CDN or reverse proxy sits between the client and the upload receiver without explicit bypass rules. Resumable.js uses GET requests to test whether a chunk already exists on the server. These test requests look like ordinary cacheable GETs to any intermediary that doesn't know better.

The fix is blunt and non-negotiable:

Cache-Control: no-store

Apply this header to every response from your upload endpoint—both GET (chunk test) and POST (chunk upload) methods. Not no-cache, not private, not max-age=0. Use no-store. It tells every intermediary in the chain—browser cache, CDN edge, reverse proxy—to never retain this response. The semantics of no-store are defined precisely in the Cloudflare Cache-Control documentation, and the behavior is consistent across all major CDN providers.

// Express example
app.use('/api/upload', (req, res, next) => {
  res.set('Cache-Control', 'no-store');
  next();
});
# Nginx example
location /api/upload {
    add_header Cache-Control "no-store" always;
    proxy_pass http://upload_backend;
}

The always keyword in Nginx matters. Without it, the header only applies to successful responses. You need it on 4xx and 5xx responses too.

Cache Static Assets Aggressively

Everything that isn't your upload endpoint should be cached as hard as possible. JavaScript bundles, CSS, images, fonts—these are immutable between deployments and benefit enormously from long TTLs.

Cache-Control: public, max-age=31536000, immutable

That's one year. The immutable directive tells browsers not to revalidate even on a hard refresh. Combined with filename hashing (e.g., app.a3f9c2.js), this is safe because the URL itself changes whenever the content changes.

A split caching strategy

Traffic typeCache-ControlCDN behaviorTTL
Upload API (/api/upload)no-storeBypass cache entirely0
Static JS/CSS bundlespublic, max-age=31536000, immutableCache at edge1 year
HTML pagespublic, max-age=300, s-maxage=3600Cache at edge, short browser TTL5 min browser / 1 hr edge
Assembled download filesprivate, no-cacheNot cached at edgeRevalidate every request

This table represents the architecture you should target. Two entirely separate caching policies coexisting on the same domain.

CDN Cache Rules

Most CDN providers let you create rules based on path patterns. Set up an explicit bypass rule for your upload path before any wildcard caching rules apply.

On Cloudflare, a Page Rule or Cache Rule matching /api/upload/* with "Cache Level: Bypass" ensures the edge never stores responses from that path. On AWS CloudFront, use a behavior with CachePolicyId set to the managed CachingDisabled policy for your upload origin path.

The order matters. CDN rule evaluation is typically first-match. If a broad /* caching rule sits above your upload bypass rule, the bypass never fires.

Cache Busting for Assembled Files

After Resumable.js finishes uploading all chunks and your server assembles the final file, you may serve that file back to the user (for preview, download, or further processing). These assembled files present a caching puzzle: the URL might be stable (/files/abc123), but the content only becomes available after assembly.

Two approaches work well:

Content-addressable URLs

Use a hash of the file content in the URL: /files/sha256-a3f9c2d1.... The content never changes for a given URL, so you can cache it indefinitely. This is the cleanest approach but requires your application to redirect from a logical identifier to the content-addressed URL.

ETag-based revalidation

Serve assembled files with an ETag header derived from the file's checksum:

ETag: "sha256-a3f9c2d1e4b5..."
Cache-Control: private, no-cache

The no-cache directive forces the browser to revalidate on every request, but if the ETag matches, the server responds with a 304 and zero bytes transferred. This pattern works well when files might be re-uploaded (versioned) at the same logical URL.

Common Caching Mistakes

Using max-age=0 instead of no-store. They're not the same. max-age=0 allows caching but requires revalidation. A misbehaving intermediary might still serve a stale response during revalidation. no-store means don't store it at all.

Forgetting query strings. Some CDNs strip query strings before computing cache keys. Since Resumable.js chunk test requests encode chunk metadata in query parameters (resumableChunkNumber, resumableIdentifier, etc.), a CDN that ignores query strings would collapse all chunk test responses into a single cached entry. Verify that your CDN's cache key includes the full query string, or—better yet—bypass caching entirely for the upload path.

Caching error responses. A 500 error from your upload endpoint, cached for 60 seconds at the CDN edge, means every chunk upload fails for a full minute even after the server recovers. The no-store directive prevents this, but double-check with your CDN's documentation—some providers require explicit configuration to avoid caching error codes.

Monitoring Cache Behavior

Add cache-diagnostic headers to your responses. Cloudflare adds CF-Cache-Status automatically. For your own infrastructure, consider a custom header:

X-Cache: HIT | MISS | BYPASS

Log these values. If you ever see HIT on an upload endpoint response, something is misconfigured and needs immediate attention. A dashboard alert on X-Cache: HIT for any request to /api/upload is a simple but effective safety net.

Caching strategy for Resumable.js deployments isn't complicated—it's binary. Static assets get cached. Upload endpoints don't. The complexity lives in making sure every layer of your infrastructure respects that boundary without exception.