Cache-Control Headers

Controlling browser and CDN caching behavior

The Cache-Control header tells browsers and CDNs how to cache responses. Proper caching dramatically improves performance by avoiding unnecessary network requests.

Caching is set by the server in response headers. The client can also send Cache-Control to request fresh data.

Common Cache-Control Directives

Directive Description Example Use
max-age=N Cache for N seconds Static assets, API responses
no-cache Must revalidate before using cache HTML pages, user-specific data
no-store Never cache (sensitive data) Bank statements, passwords
private Only browser can cache (not CDN) User profiles, dashboards
public Any cache can store (CDN-friendly) Images, CSS, JS bundles
immutable Never changes, don't revalidate Versioned assets (app.abc123.js)
stale-while-revalidate=N Serve stale while fetching fresh Background updates

Server: Setting Cache Headers (Node.js)

import { createServer } from 'node:http'; import { readFile } from 'node:fs/promises'; const server = createServer(async (req, res) => { const url = req.url; // Static assets: Cache for 1 year (immutable) if (url.match(/\.(js|css|png|jpg|woff2)$/)) { const file = await readFile(`./public${url}`); res.writeHead(200, { 'Content-Type': getMimeType(url), 'Cache-Control': 'public, max-age=31536000, immutable' }); res.end(file); return; } // HTML: Always revalidate if (url.endsWith('.html') || url === '/') { const html = await readFile('./public/index.html'); res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' }); res.end(html); return; } // API: Short cache with background refresh if (url.startsWith('/api/')) { res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60, stale-while-revalidate=300' }); res.end(JSON.stringify({ data: '...' })); return; } // User data: Private, no CDN if (url.startsWith('/user/')) { res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'private, max-age=0, must-revalidate' }); res.end(JSON.stringify({ user: '...' })); } }); server.listen(3000);

Common Caching Strategies

// 1. STATIC ASSETS (images, fonts, bundled JS/CSS) // Cache forever, use content hash in filename to bust cache 'Cache-Control: public, max-age=31536000, immutable' // Files: app.abc123.js, styles.def456.css // 2. HTML PAGES // Don't cache, or always revalidate 'Cache-Control: no-cache' // Browser stores it but checks freshness each time // 3. API DATA // Short TTL with stale-while-revalidate for snappy UX 'Cache-Control: public, max-age=60, stale-while-revalidate=600' // Serves cached response while fetching fresh one in background // 4. PERSONALIZED CONTENT // Private (not CDN cached), short or no cache 'Cache-Control: private, max-age=0' // User dashboards, account settings // 5. SENSITIVE DATA // Never store anywhere 'Cache-Control: no-store' // Financial data, passwords, tokens // 6. CDN EDGE CACHING // Different TTLs for browser vs CDN 'Cache-Control: public, max-age=60, s-maxage=3600' // Browser: 1 minute, CDN: 1 hour

Client: Requesting Fresh Data

// Force bypass cache (browser's cached copy) const response = await fetch('/api/data', { cache: 'no-store' // Don't use any cache }); // Fetch API cache modes: // 'default' - Normal caching rules // 'no-store' - Bypass cache completely // 'reload' - Fetch from network, update cache // 'no-cache' - Validate with server first // 'force-cache' - Use cache even if stale // Example: Always get fresh data async function fetchFresh(url) { return fetch(url, { cache: 'no-store' }); } // Example: Prefer cache but fetch if not available async function fetchCachedFirst(url) { return fetch(url, { cache: 'force-cache' }); }

The cache option in fetch controls how the browser uses its HTTP cache. This is different from the server's Cache-Control header.

Cache Busting

When using long max-age, include a hash in the filename (e.g., app.abc123.js). When the file changes, the hash changes, creating a new URL that bypasses the old cache. Build tools like Vite and webpack do this automatically.