You're Paying Your CDN to Forget Everything: A Guide to Caching That Actually Works
CDNs are supposed to be the easy win. You put one in front of your app, assets get cached at edge nodes close to your users, and bandwidth costs drop. That's the pitch. The reality, for a lot of teams, looks more like this: you're paying $800 a month for a CDN that's serving a cache hit rate of 35%, re-fetching your main JavaScript bundle on every deploy, and invalidating entire cache namespaces when a single CSS file changes.
At that point, the CDN isn't saving you money. It's just adding a layer of complexity between you and the problem.
What Cache Busting Is Supposed to Do
The core idea is simple: browsers and CDN edge nodes cache files based on their URL. If the URL doesn't change, the cached version gets served — even if the underlying file has been updated. Cache busting solves this by appending a version identifier to the URL, typically a hash of the file's contents. Something like main.abc123.js. When the file changes, the hash changes, the URL changes, and caches treat it as a brand-new asset.
This is correct behavior. This is what you want. The problem comes when teams implement cache busting sloppily, too aggressively, or without understanding what they're actually invalidating.
The Three Ways Cache Busting Backfires
1. Whole-bundle hashing instead of chunk-level hashing.
This is the big one. If your build system generates a single hashed filename for your entire JavaScript bundle, then any code change — even fixing a typo in a comment — changes the hash on the entire bundle. Every user who visits your site after a deploy has to download the whole thing again, even if 95% of it is identical to what they had cached.
The fix is code splitting and chunk-level hashing. Separate your vendor dependencies (React, third-party libraries) from your application code. Vendor bundles rarely change. Your app code changes constantly. With proper chunking, a deploy that touches only application logic results in users re-downloading only the app chunk — the vendor bundle stays cached. That's potentially megabytes of data your users don't have to pull again.
2. Aggressive manual cache invalidation.
A lot of teams have a deploy script that fires a blanket cache invalidation against their CDN at the end of every release. "Clear everything, just to be safe." This is expensive in two ways: you pay for the invalidation requests (AWS CloudFront charges per invalidation path), and you immediately force your CDN edge nodes to pull fresh copies of every asset from your origin — which means your origin servers get hammered with traffic right after every deploy, and your users experience higher latency until those caches warm back up.
Selective invalidation — only clearing the specific files that actually changed — is almost always the right call. Your hashed static assets don't need invalidation at all, because the URL changes with the content. Manual invalidation is mostly for HTML files and other non-versioned URLs.
3. Incorrect or missing Cache-Control headers.
This one shows up constantly in audits. Teams set up a CDN, forget to configure cache headers on the origin, and end up with CDN-level caching controlled by default rules — which are often conservative. Assets that should be cached for a year are getting a 1-hour TTL. Or worse, Cache-Control: no-cache is leaking onto static assets because someone copied a header config from a dynamic API route.
Run your site through curl -I https://yourdomain.com/static/main.abc123.js and look at what comes back. If you don't see Cache-Control: public, max-age=31536000, immutable on your hashed static assets, you're leaving cache efficiency on the table.
Calculating the Actual ROI of Your Caching Setup
Here's a framework for figuring out whether your CDN is actually earning its keep:
Step 1: Find your cache hit ratio. Every major CDN exposes this in its analytics dashboard. Cloudflare calls it "Cache Hit Rate." CloudFront shows it as a percentage in its metrics. Anything below 70% for a content-heavy site is worth investigating. Static asset sites should be hitting 85–95%.
Step 2: Calculate your origin egress cost. Every cache miss means a request goes back to your origin. If your origin is on AWS, GCP, or Azure, that's egress bandwidth being billed. Multiply your monthly cache miss count by your average response payload size and your per-GB egress rate. That number is what poor caching is costing you every month.
Step 3: Factor in Time to First Byte (TTFB) for misses. Cache misses don't just cost money — they cost user experience. An asset served from a warm CDN edge might have a TTFB of 10–30ms. The same asset served from your origin after a miss could be 200–500ms. If your cache hit rate is low, a meaningful percentage of your users are experiencing that slower path.
Step 4: Audit your invalidation spend. Pull your CDN billing breakdown and find the line item for cache invalidations. If it's more than 5–10% of your total CDN spend, you're probably over-invalidating.
What a Healthy Caching Strategy Looks Like
Hashed static assets (JS, CSS, images with fingerprinted filenames) get max-age=31536000, immutable. They never get manually invalidated — the URL changes when the content changes, so there's nothing to invalidate.
HTML files and API responses that need to stay fresh get shorter TTLs, stale-while-revalidate where appropriate, and precise invalidation triggered only by actual content changes.
Deploy pipelines use content-aware cache invalidation — only touching the URLs that correspond to files that actually changed in the build output. Tools like rclone with --checksum flags or CDN-native deploy integrations (Vercel, Netlify, Cloudflare Pages) handle this automatically.
The goal isn't to cache everything forever or to bust caches constantly. It's to make deliberate decisions at the asset level, measure the outcomes, and stop paying your CDN to forget things it should be remembering.