Web Caching
Master browser caching, Service Workers, CDN, and understand server-side caching architecture for blazing-fast web applications.
What is Caching?
Caching is storing a copy of data closer to where it's needed, so you don't fetch it from the slow source every time.
WITHOUT CACHE: User โโโโโโโบ Server โโโโโโโบ Database Every. Single. Time. 100ms 50ms Total: 150ms WITH CACHE: User โโโโโโโบ Cache HIT! Return instantly 5ms Total: 5ms (30x faster!)
| Metric | Without Cache | With Cache | Improvement |
|---|---|---|---|
| Response Time | 150-500ms | 5-50ms | 10-30x faster |
| Server Load | 100% | 10-20% | 80% reduction |
| Database Queries | Every request | Cache misses only | 90% reduction |
The Caching Layers
There are multiple layers where caching happens. The closer to the user, the faster!
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ USER'S DEVICE โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ โ Memory โ โ Browser โ โ Service โ โ โ โ Cache โ โ HTTP โ โ Worker โ โ โ โ (fastest) โ โ Cache โ โ Cache โ โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ CDN โ โ (Edge servers distributed globally) โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ APPLICATION SERVER โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ โ In-Memory โ โ Redis/ โ โ โ โ Cache โ โ Memcached โ โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ DATABASE โ โ (Query cache, Buffer pool) โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Cache as close to the user as possible! Browser > CDN > Server > Database
HTTP Cache Headers
The server tells the browser HOW to cache using HTTP response headers:
| Header | Purpose |
|---|---|
Cache-Control |
Main header - controls all caching behavior |
ETag |
Unique version ID for the resource |
Last-Modified |
When the resource was last changed |
Expires |
Absolute expiry date (legacy, use max-age) |
Vary |
Cache different versions based on request headers |
Cache-Control Deep Dive
Cache-Control: max-age=3600, public โ โ โ โโโ WHO can cache โโโ HOW LONG to cache (seconds)
Freshness Directives
# Cache for 1 hour Cache-Control: max-age=3600 # Cache for 1 year (static assets with hash) Cache-Control: max-age=31536000 # CDN caches longer than browser Cache-Control: max-age=60, s-maxage=3600 # Browser: 60 seconds, CDN: 1 hour
Cacheability Directives
# Anyone can cache (browser, CDN, proxies) Cache-Control: public, max-age=3600 # Only browser can cache (not CDN) - for user-specific content Cache-Control: private, max-age=3600 # Don't cache anywhere - sensitive data! Cache-Control: no-store # Cache but always revalidate with server first Cache-Control: no-cache
Advanced Directives
# Serve stale while fetching fresh in background (amazing for UX!) Cache-Control: max-age=60, stale-while-revalidate=3600 # Content NEVER changes (versioned files like app.abc123.js) Cache-Control: max-age=31536000, immutable # Serve stale if origin server is down Cache-Control: max-age=60, stale-if-error=86400
Cache-Control by Content Type
| Content | Cache-Control | Why |
|---|---|---|
Hashed assets (app.abc123.js) |
public, max-age=31536000, immutable |
Never changes |
Static assets (favicon.ico) |
public, max-age=86400 |
Changes rarely |
| HTML pages | no-cache |
Always check for updates |
| Public API | public, max-age=300, s-maxage=600 |
Fresh enough |
| Private API | private, max-age=60 |
User-specific |
| Sensitive data | no-store |
Never cache! |
ETag & Validation
When cache expires, the browser asks: "Is my copy still good?" This is called revalidation.
FIRST REQUEST (no cache yet): Browser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ Server GET /style.css Browser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Server 200 OK ETag: "abc123" Cache-Control: max-age=3600 [CSS content...] AFTER CACHE EXPIRES (revalidating): Browser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ Server GET /style.css If-None-Match: "abc123" โ "Is abc123 still valid?" Server checks: Is "abc123" still current? IF NOT MODIFIED: Browser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Server 304 Not Modified (no body - saves bandwidth!) IF MODIFIED: Browser โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Server 200 OK ETag: "xyz789" [New CSS content...]
The 304 response contains no body - just headers. This saves bandwidth while still ensuring fresh content!
Service Workers
Service Workers give you full programmatic control over caching. They sit between your app and the network, intercepting every request.
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ Your App โ โโโโโโโบ โ Service Worker โ โโโโโโโบ โ Network โ โ โ โ โ โ โ โโโโโโโโโโโโโโโโ โ "I intercept ALL โ โโโโโโโโโโโโโโโโ โ requests and โ โ decide what to โ โ return!" โ โ โ โ โโโโโโโโโโโโโโโโ โ โ โ Cache API โ โ โ โ (Storage) โ โ โ โโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโ
Key Capabilities
- Offline Support - Serve cached content when network is unavailable
- Custom Caching Strategies - You decide: cache-first, network-first, or hybrid
- Background Sync - Queue failed requests and retry when online
- Push Notifications - Receive messages even when app is closed
Service Worker Lifecycle
1. REGISTER โโโ navigator.serviceWorker.register('/sw.js') โ โผ 2. INSTALL (first time or when updated) โโโ Pre-cache static assets โโโ self.skipWaiting() to activate immediately โ โผ 3. ACTIVATE โโโ Clean up old caches โโโ self.clients.claim() to control pages immediately โ โผ 4. RUNNING โโโ Intercepts ALL fetch requests โโโ You decide: cache, network, or both!
Registration Code
// In your main app JavaScript if ('serviceWorker' in navigator) { window.addEventListener('load', async () => { try { // Register the service worker file const registration = await navigator.serviceWorker.register('/sw.js'); console.log('SW registered! Scope:', registration.scope); } catch (error) { console.error('SW registration failed:', error); } }); }
Service Worker File (sw.js)
const CACHE_NAME = 'my-app-v1'; // Files to cache immediately on install const PRECACHE_ASSETS = [ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/offline.html' ]; // INSTALL - Pre-cache static assets self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.addAll(PRECACHE_ASSETS)) .then(() => self.skipWaiting()) ); }); // ACTIVATE - Clean up old caches self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames .filter(name => name !== CACHE_NAME) .map(name => caches.delete(name)) ); }).then(() => self.clients.claim()) ); }); // FETCH - Intercept requests (implement strategy here) self.addEventListener('fetch', (event) => { event.respondWith(handleRequest(event.request)); });
Caching Strategies
1. Cache First
Check cache โ Return if found โ Otherwise fetch network
2. Network First
Try network โ If fails, return from cache
3. Stale While Revalidate
Return cached immediately โ Update cache in background
4. Network Only
Always fetch from network, no caching
Cache First Implementation
// Best for: Static assets that rarely change async function cacheFirst(request) { // 1. Check cache first const cached = await caches.match(request); if (cached) { console.log('Cache HIT:', request.url); return cached; } // 2. Not in cache - fetch from network console.log('Cache MISS, fetching:', request.url); const response = await fetch(request); // 3. Cache for next time const cache = await caches.open(CACHE_NAME); cache.put(request, response.clone()); // Clone because response is one-time use return response; }
Stale While Revalidate Implementation
// Best for: Content where speed matters but freshness is nice async function staleWhileRevalidate(request) { const cache = await caches.open(CACHE_NAME); const cached = await caches.match(request); // Fetch fresh version in background (don't await!) const fetchPromise = fetch(request).then(response => { cache.put(request, response.clone()); return response; }); // Return cached immediately OR wait for network return cached || fetchPromise; }
Strategy Selection Guide
| Content Type | Strategy | Why |
|---|---|---|
| App Shell (HTML) | Stale While Revalidate | Fast + stays fresh |
| CSS/JS bundles | Cache First | Rarely change (use versioned URLs) |
| Images, Fonts | Cache First | Large files, rarely change |
| API - User data | Network First | Must be fresh |
| API - Static data | Stale While Revalidate | Speed + eventual freshness |
| Real-time data | Network Only | Can't be stale |
Offline Support
Show a custom offline page when the network is unavailable:
async function handleRequest(request) { try { return await fetch(request); } catch (error) { // Network failed! Return appropriate fallback // For HTML pages - show offline page if (request.headers.get('Accept').includes('text/html')) { return caches.match('/offline.html'); } // For images - show placeholder if (request.url.match(/\.(png|jpg|jpeg|gif|svg)$/)) { return caches.match('/images/offline-placeholder.png'); } // For API - return error JSON if (request.url.includes('/api/')) { return new Response( JSON.stringify({ error: 'You are offline' }), { status: 503, headers: { 'Content-Type': 'application/json' } } ); } } }
CDN Caching
CDNs cache your content at edge servers worldwide, serving users from the nearest location.
WITHOUT CDN: User in Tokyo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโบ Server in New York ~200ms latency WITH CDN: User in Tokyo โโโโบ Edge in Tokyo ~10ms Cache HIT! โ Return immediately Cache MISS โ Fetch from origin, cache it
Major CDN Providers
| Provider | Edge Locations | Best For |
|---|---|---|
| Cloudflare | 300+ | Free tier, Workers, DDoS protection |
| AWS CloudFront | 400+ | AWS integration, Lambda@Edge |
| Fastly | 70+ | Real-time purging, edge compute |
| Vercel Edge | 100+ | Next.js apps, serverless |
Cache Invalidation Strategies
Versioned URLs
/app.js โ /app.abc123.js
Purge API
Call CDN API to invalidate specific URLs
Short TTL + SWR
max-age=60, stale-while-revalidate=3600
Server-Side Caching (Architecture)
Understanding how backend caching works helps you collaborate with backend teams.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ APPLICATION SERVER โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ โ โ 1. IN-PROCESS CACHE โ โ โโโ Stored in server's RAM (~0.01ms) โ โ โโโ NOT shared between servers โ โ โโโ Lost when server restarts โ โ โ โ 2. DISTRIBUTED CACHE (Redis/Memcached) โ โ โโโ Separate cache server (~1ms) โ โ โโโ Shared between all app servers โ โ โโโ Survives server restarts โ โ โ โ 3. DATABASE โ โ โโโ Source of truth (~10-50ms) โ โ โโโ Query cache, buffer pool โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Redis vs Memcached
| Feature | Redis | Memcached |
|---|---|---|
| Data types | Rich (hash, list, set, sorted set) | String only |
| Persistence | Yes (optional disk backup) | No (memory only) |
| Replication | Built-in master-slave | No built-in |
| Use case | General purpose, sessions, queues | Simple caching |
Most companies use Redis - it has more features and is still blazing fast (100k+ operations/second).
Caching Patterns
Cache-Aside (Lazy Loading)
App checks cache โ Miss โ Fetch DB โ Store in cache
Read-Through
App always asks cache โ Cache handles DB fetch
Write-Through
Write to cache โ Cache writes to DB โ Confirm
Write-Behind
Write to cache โ Confirm immediately โ Async DB write
When a popular cache key expires, thousands of requests hit the database simultaneously! Solutions: locking, early expiration, or stale-while-revalidate.
How Big Companies Cache
๐ฌ YouTube / Netflix
VIDEO STREAMING STRATEGY: 1. Videos cached at 1000s of edge locations globally 2. Popular videos pushed to edges BEFORE users request 3. Servers placed INSIDE ISP data centers 4. Multiple quality versions cached (360p, 720p, 1080p, 4K) RESULT: 95%+ of traffic served from edge, <10ms latency
๐ Amazon / Flipkart
E-COMMERCE CACHING: โโโ Product catalog: Long TTL, invalidate on update โโโ Prices: Short TTL (can't show stale prices!) โโโ Inventory: Real-time or very short TTL โโโ Static assets: Infinite cache with versioned URLs โโโ Search results: Cache by query + location โโโ Recommendations: Pre-computed, cached per user segment CHALLENGE: Personalization makes caching harder
๐ฆ Twitter / X
SOCIAL MEDIA CACHING: TIMELINE: โโโ Fan-out on write: Pre-compute timelines for users โโโ Celebrity exception: Fan-out on read (too many followers) TWEETS: โโโ Immutable! Cache forever โโโ Only engagement counts update TRENDS: โโโ Computed periodically, cached per region
Best Practices Checklist
Frontend Caching
- Use versioned URLs for static assets (
app.abc123.js) - Set
Cache-Control: immutablefor versioned assets - Use
stale-while-revalidatefor HTML pages - Implement Service Worker for offline support
- Pre-cache critical assets on SW install
- Use appropriate strategy per content type
- Monitor cache hit rates in DevTools
HTTP Headers
- Always set
Cache-Control(not justExpires) - Use
max-agefor browser,s-maxagefor CDN - Set
ETagorLast-Modifiedfor validation - Use
Varyheader correctly (avoidVary: *) - Set
no-storefor sensitive data
Golden Rules
- Cache as close to user as possible - Browser > CDN > Server
- Static = aggressive, Dynamic = careful
- Have invalidation strategy BEFORE caching
- When in doubt, shorter TTL is safer
- Monitor! If cache isn't hit, remove it