HTTP Caching Explained

Proper caching makes sites fast. Here's how HTTP caching works and how to use it.

Why Caching

Without caching: Every request hits your server. Every asset redownloads.

With caching: Browsers store responses. Subsequent requests are instant.

Cache-Control Header

The main way to control caching:

Cache-Control: max-age=3600

"Cache this for 1 hour (3600 seconds)."

Directives

max-age=N          # Cache for N seconds
no-cache           # Must revalidate before using cached version
no-store           # Don't cache at all (sensitive data)
private            # Only browser can cache (not CDNs)
public             # Anyone can cache (including CDNs)
immutable          # Never changes (with versioned URLs)

Static Assets

For JS, CSS, images with hashed filenames:

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

One year. The hash changes when content changes, so it's safe.

// Express
app.use('/static', express.static('public', {
  maxAge: '1y',
  immutable: true
}));

HTML Pages

Usually don't cache aggressively:

Cache-Control: no-cache

Browser must check if content changed before using cached version.

API Responses

Depends on the data:

// User-specific data
res.set('Cache-Control', 'private, no-cache');

// Public data that updates hourly
res.set('Cache-Control', 'public, max-age=3600');

// Real-time data
res.set('Cache-Control', 'no-store');

ETags

Content fingerprint for validation:

Response:
ETag: "abc123"

Next request:
If-None-Match: "abc123"

Response (if unchanged):
304 Not Modified

Browser uses cached version if ETag matches. No body transferred.

Express Example

const express = require('express');
const app = express();

// Static files with long cache
app.use('/assets', express.static('dist/assets', {
  maxAge: '1y',
  immutable: true
}));

// API with short cache
app.get('/api/products', (req, res) => {
  res.set('Cache-Control', 'public, max-age=300'); // 5 minutes
  res.json(products);
});

// No cache for user data
app.get('/api/me', auth, (req, res) => {
  res.set('Cache-Control', 'private, no-store');
  res.json(req.user);
});

CDN Caching

CDNs (Cloudflare, Vercel, etc.) cache based on these headers too.

Cache-Control: public, s-maxage=3600, max-age=300
  • `s-maxage`: CDN caches for 1 hour
  • `max-age`: Browser caches for 5 minutes

Testing

curl -I https://example.com/style.css

Look for `Cache-Control`, `ETag`, `Last-Modified` headers.

Common Patterns

| Content | Cache-Control | |---------|---------------| | Versioned assets | `public, max-age=31536000, immutable` | | HTML pages | `no-cache` or short `max-age` | | API (public) | `public, max-age=300` | | API (private) | `private, no-cache` | | Real-time data | `no-store` |

Get caching right and your site feels instant.

Back to Journal