"There are only two hard things in Computer Science: cache invalidation and naming things." That famous quote exists because caching is simultaneously the most impactful performance optimization and the trickiest to get right. Done well, caching can make your site 10-100× faster. Done poorly, it serves stale data to your users.

The Caching Layers

A typical web request passes through multiple caching layers:

  • Browser Cache: Closest to the user. Stores assets locally on their device.
  • CDN Cache: Distributed edge servers worldwide. Serves content from the nearest geographic location.
  • Reverse Proxy Cache: Nginx, Varnish, or Cloudflare sitting in front of your application server.
  • Application Cache: In-memory stores like Redis or Memcached for database query results, API responses, computed values.
  • Database Cache: Query cache and buffer pool built into the database engine.

Browser Cache Headers

Cache-Control Header
# Static assets (CSS, JS, images) — cache aggressively
Cache-Control: public, max-age=31536000, immutable

# HTML pages — don't cache, always revalidate
Cache-Control: no-cache

# API responses — cache for 5 minutes
Cache-Control: private, max-age=300

# Sensitive data — never cache
Cache-Control: no-store

Key Directives

  • public — Can be cached by browsers AND intermediate caches (CDNs, proxies)
  • private — Only the user's browser can cache it (not CDNs)
  • max-age=N — Cache for N seconds
  • no-cache — Cache it, but always revalidate with the server before using
  • no-store — Don't cache at all. Use for sensitive data.
  • immutable — The content will never change (used with fingerprinted filenames)

Cache Busting with Fingerprinted Filenames

The ideal caching strategy for static assets: give files a unique hash in the filename and cache them forever.

Fingerprinted Assets
<!-- Instead of this (cache invalidation nightmare): -->
<link rel="stylesheet" href="/css/style.css">

<!-- Use this (infinitely cacheable): -->
<link rel="stylesheet" href="/css/style.a3f9b2c1.css">

<!-- When the CSS changes, the hash changes, so the browser
     fetches the new file automatically -->

Build tools like Vite, Webpack, and esbuild generate these fingerprinted filenames automatically.

CDN Caching

A CDN (Content Delivery Network) caches your content on edge servers around the world. When a user in Tokyo requests your image, it's served from a server in Tokyo instead of your origin server in London.

  • Static assets: Cache at the CDN edge with long max-age + fingerprinted filenames
  • HTML pages: Cache with short TTL (e.g., 60 seconds) or use stale-while-revalidate
  • API responses: Cache GETs for public data, never cache POST/PUT/DELETE

Server-Side Caching with Redis

Redis Caching Pattern (Node.js)
import Redis from 'ioredis';
const redis = new Redis();

async function getUser(userId) {
  const cacheKey = `user:${userId}`;

  // 1. Check cache first
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // 2. Cache miss — query database
  const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);

  // 3. Store in cache (expire after 5 minutes)
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 300);

  return user;
}

// Invalidate on update
async function updateUser(userId, data) {
  await db.query('UPDATE users SET ... WHERE id = $1', [userId]);
  await redis.del(`user:${userId}`); // Clear cache
}

Cache Invalidation Strategies

  • TTL (Time-To-Live): Cache expires after a fixed time. Simple but may serve stale data.
  • Write-Through: Update the cache whenever you update the database. Consistent but adds write latency.
  • Write-Behind: Update the cache immediately, flush to database asynchronously. Fast but risky.
  • Cache-Aside: Application manages the cache manually (the Redis example above). Most common pattern.
  • Event-Driven: Database changes emit events that invalidate relevant caches. Most robust for distributed systems.

Try Our Free Performance Tools

Minify your CSS, JavaScript, and HTML to reduce file sizes.