CDN Caching Explained: What Every Developer Should Know
Last week, I pushed a CSS update to fix a button color. Five minutes later, I got a message: "Hey, the button still looks wrong." I checked—it looked fine to me. I cleared my cache, still fine on my end. I asked them to hard refresh, and bam, it was fixed.
Sound familiar? Yeah, caching can be a pain. But once you understand how it actually works, you'll save yourself countless hours of "have you tried clearing your cache?" conversations.
Let's demystify CDN caching once and for all.
What Actually Happens When a CDN Caches Your Content
Think of a CDN as a network of helpful assistants stationed around the world. When someone requests your image or JavaScript file, instead of traveling all the way to your origin server in Virginia, they get it from an edge server in Frankfurt (or wherever is closest to them).
Here's a simplified view of how it works:
User Request → Nearest Edge Server → (Cache Hit?) → Return Cached Content
↓ (Cache Miss)
Origin Server → Store & Return ContentBut here's the thing—these edge servers need to know two things:
- Should I store this?
- How long should I keep it?
This is where cache headers come in.
Browser vs. CDN Caching: What's the Difference?
Before diving into headers, it's worth understanding that caching happens at multiple layers:
- Browser Cache: Stores files locally on the user's device. Controlled by the same headers, but only benefits that specific user.
- CDN Cache: Stores files on edge servers worldwide. Benefits all users in that region and reduces load on your origin server.
Both respect Cache-Control headers, but CDN caching has a multiplier effect—one cached response serves thousands of users.
The Cache Headers You Actually Need to Know
There are a lot of HTTP headers related to caching, but let's focus on the ones that matter most.
Cache-Control: The Boss
Cache-Control is the header that tells browsers and CDNs what to do. Here are the directives you'll use 90% of the time:
import path from 'path'
// In your Node.js/Express server
app.get('/static/logo.png', (req, res) => {
res.set('Cache-Control', 'public, max-age=31536000, immutable')
res.sendFile(path.join(__dirname, 'static/logo.png'))
})Let's break that down:
public— Anyone can cache this (browsers, CDNs, proxies)max-age=31536000— Cache for 1 year (31,536,000 seconds)immutable— This file will never change (so don't bother revalidating)
For content that changes more frequently:
app.get('/api/user-profile', (req, res) => {
res.set('Cache-Control', 'private, max-age=0, must-revalidate')
res.json(userData)
})Here:
private— Only the user's browser should cache this (not CDNs)max-age=0— Consider it stale immediatelymust-revalidate— Always check with the server before using cached version
A note on revalidation: When a cache revalidates, it typically uses ETag or Last-Modified headers to ask the server "has this changed?" If not, the server responds with a quick 304 Not Modified, saving bandwidth.
TTL: Time To Live
Speaking of max-age, let's talk about TTL, which is basically the same concept but with a CDN twist. TTL is just another way of saying "how long should this be cached?" CDN providers often use this term in their dashboards and APIs.
A quick cheat sheet for common TTLs:
| Content Type | Recommended TTL | Why |
|---|---|---|
| Versioned assets (app.a1b2c3.js) | 1 year (31536000s) | Filename changes when content changes |
| Images | 1 week to 1 month | Balance freshness with performance |
| HTML pages | 0 to 5 minutes | References other assets; needs to stay fresh |
| Public API responses (weather, etc.) | 60 seconds | Data changes but not per-request |
| Private API responses (user data) | 0 (no-store) | Security and privacy concerns |
Setting Cache Headers in Practice
Here's a more complete example of how I typically set up caching in a Node.js application:
import express from 'express'
const app = express()
// Static assets with hash in filename - cache forever
// Note: '1y' is Express shorthand for 31536000 seconds
app.use('/assets', express.static('dist/assets', {
maxAge: '1y',
immutable: true,
}))
// Images that might change occasionally
// '7d' = 604800 seconds (one week)
app.use('/images', express.static('public/images', {
maxAge: '7d',
}))
// HTML files - short cache or none
app.use(express.static('public', {
maxAge: '5m',
setHeaders: (res, filePath) => {
if (filePath.endsWith('.html')) {
res.set('Cache-Control', 'no-cache')
}
},
}))The Dreaded Cache Invalidation Problem
Phil Karlton famously said there are only two hard things in computer science: cache invalidation and naming things. He wasn't wrong.
When you update a file, how do you tell all those edge servers around the world to forget the old version? You have a few options:
Option 1: Cache Busting with Versioned Filenames
This is the most reliable approach. Instead of app.js, you serve app.a1b2c3d4.js. When the content changes, the filename changes, and the CDN treats it as a completely new file.
Pros: Bulletproof—no stale content possible
Cons: Requires build tool setup and careful HTML management
Most build tools do this automatically:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash].[ext]',
},
},
},
})Option 2: Purge the Cache
Sometimes you can't change filenames—maybe it's a logo that needs to stay at /logo.png. In that case, you need to tell your CDN to purge that specific file.
Pros: Works for any file, instant updates Cons: Requires API integration, may have rate limits
Most CDNs provide a dashboard or API for cache invalidation. Check your CDN provider's documentation for specific instructions on how to purge cached content.
Option 3: Use Short TTLs
For content that changes unpredictably, just use shorter cache times. A 5-minute TTL means the worst case is someone sees stale content for 5 minutes. Often that's totally acceptable.
Pros: Simple, no extra tooling needed
Cons: More origin requests, slightly higher latency
Common Caching Mistakes (I've Made Them All)
Even with these strategies, it's easy to trip up. Here are some mistakes I've learned the hard way:
Mistake 1: Caching HTML pages for too long
Your HTML references your JavaScript and CSS files. If the HTML is cached but you've deployed new assets, users might get mismatched versions. Keep HTML cache times short or use no-cache.
Mistake 2: Forgetting about query strings
Some CDNs treat image.png and image.png?v=2 as different files. Others ignore query strings entirely. Know how your CDN handles this before relying on query strings for cache busting.
Mistake 3: Not setting any cache headers
If you don't set cache headers, browsers and CDNs will make their own decisions. That's usually not what you want. Be explicit about your caching strategy.
Quick Reference: My Go-To Cache Settings
const cacheSettings = {
// Hashed static assets
hashedAssets: 'public, max-age=31536000, immutable',
// Regular images
images: 'public, max-age=604800', // 1 week
// HTML pages
html: 'no-cache', // Always revalidate
// API responses (public data)
// stale-while-revalidate: serve stale content for up to 5 minutes
// while fetching fresh data in the background
publicApi: 'public, max-age=60, stale-while-revalidate=300',
// API responses (private data)
privateApi: 'private, no-store',
}Wrapping Up
Caching doesn't have to be scary. Remember these key points:
- Use
Cache-Controlto tell browsers and CDNs what to do - Version your static assets so you can cache them forever
- Keep HTML cache times short
- Have a cache invalidation strategy for when things go wrong
Getting caching right can dramatically improve your site's performance and reduce your server load. And best of all, it'll cut down on those endless support tickets asking users to clear their cache.
What's your biggest caching headache? I'd love to hear about it—sometimes the trickiest problems make for the best learning opportunities.
If you're looking for a CDN that makes caching simple, check out easyCDN. We built it specifically for indie hackers and developers who want powerful CDN features without the complexity of enterprise solutions. Just upload your assets and get back to what you do best—building cool stuff.