Caching Strategies & Best Practices
Implement effective caching strategies to dramatically reduce server load and improve page load times. Learn browser caching, CDN optimization, and advanced caching patterns.
Why Caching Matters for Performance
Caching stores copies of files or data in temporary storage locations, allowing faster retrieval on subsequent requests. Effective caching can reduce page load times by 50-80% for repeat visitors.
Benefits of Caching
- Reduced Latency: Serve cached content faster than generating it on-demand
- Lower Server Load: Fewer requests to origin server reduces CPU and database usage
- Cost Savings: Reduced bandwidth and server costs from fewer origin requests
- Better UX: Faster page loads improve user experience and engagement
- Offline Support: Service workers enable offline functionality
Types of Caching
Browser Cache
Stores resources locally on user's device. Controlled by HTTP headers (Cache-Control, Expires).
CDN Cache
Content delivery networks cache static assets at edge locations worldwide for faster global delivery.
Server Cache
Server-side caching (Redis, Memcached) stores database queries, API responses, and rendered pages.
Browser Caching with HTTP Headers
Control browser caching behavior using Cache-Control and other HTTP headers. Proper configuration ensures browsers cache static assets while always fetching fresh dynamic content.
Cache-Control Header
The modern standard for caching. Directives control how and for how long resources are cached.
# Nginx configuration for static assetslocation ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ { # Cache for 1 year (immutable files with cache busting) expires 1y; add_header Cache-Control "public, immutable";}
# HTML pages - revalidate on each requestlocation ~* \.html$ { expires -1; add_header Cache-Control "public, must-revalidate, max-age=0";}
# API responses - no cachinglocation /api/ { add_header Cache-Control "no-store, no-cache, must-revalidate";}Common Cache-Control Directives
| Directive | Description | Use Case |
|---|---|---|
| max-age=31536000 | Cache for 1 year | Versioned static assets |
| public | Can be cached by any cache | Static assets, public pages |
| private | Browser cache only | User-specific data |
| no-cache | Revalidate before use | Dynamic content |
| no-store | Never cache | Sensitive data, APIs |
| immutable | Never revalidate | Cache-busted assets |
Cache Busting Strategies
Use versioned filenames or query strings to force browsers to download new versions when files change.
<!-- Filename versioning (best) --><link rel="stylesheet" href="/styles.abc123.css" /><script src="/app.def456.js"></script>
<!-- Query string versioning (fallback) --><link rel="stylesheet" href="/styles.css?v=1.2.3" />
<!-- Webpack/Next.js automatic hashing --><!-- Output: main.8f4c2a1b.js, styles.3d9e7f2c.css -->CDN Caching Strategy
Content Delivery Networks (CDNs) cache static assets at edge locations worldwide, reducing latency for global users.
CDN Cache Configuration
# Cloudflare Page Rules example# Static assets - cache everything for 1 yearURL: yourdomain.com/static/*Cache Level: Cache EverythingEdge Cache TTL: 1 yearBrowser Cache TTL: 1 year
# HTML pages - cache for 1 hourURL: yourdomain.com/*.htmlCache Level: Cache EverythingEdge Cache TTL: 1 hourBrowser Cache TTL: 1 hour
# API endpoints - bypass cacheURL: yourdomain.com/api/*Cache Level: BypassCDN Best Practices
- Use separate cache keys for mobile/desktop if serving different content
- Enable HTTP/2 or HTTP/3 for faster multiplexed connections
- Purge cache selectively when content changes (avoid full purges)
- Set appropriate TTLs: Long for static assets (1 year), short for dynamic content (1 hour)
- Use stale-while-revalidate to serve stale content while fetching fresh
Service Worker Caching (Advanced)
Service workers provide programmatic control over caching and enable offline functionality for Progressive Web Apps.
Basic Service Worker Cache
// service-worker.jsconst CACHE_NAME = 'my-site-cache-v1';const urlsToCache = [ '/', '/styles/main.css', '/scripts/app.js', '/images/logo.png'];
// Install event - cache critical assetsself.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(urlsToCache)) );});
// Fetch event - serve from cache, fallback to networkself.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then((response) => { // Return cached version or fetch from network return response || fetch(event.request); }) );});Advanced Caching Strategies
// Strategy 1: Cache First (for static assets)self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request) .then((cached) => cached || fetch(event.request)) );});
// Strategy 2: Network First (for dynamic content)self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request) .catch(() => caches.match(event.request)) );});
// Strategy 3: Stale While Revalidateself.addEventListener('fetch', (event) => { event.respondWith( caches.open(CACHE_NAME).then((cache) => { return cache.match(event.request).then((cached) => { const fetchPromise = fetch(event.request).then((response) => { cache.put(event.request, response.clone()); return response; }); return cached || fetchPromise; }); }) );});Workbox (Recommended)
Google's Workbox library simplifies service worker development with battle-tested caching strategies.
// workbox-config.jsimport { precacheAndRoute } from 'workbox-precaching';import { registerRoute } from 'workbox-routing';import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies';import { ExpirationPlugin } from 'workbox-expiration';
// Precache build artifactsprecacheAndRoute(self.__WB_MANIFEST);
// Cache images with expirationregisterRoute( ({ request }) => request.destination === 'image', new CacheFirst({ cacheName: 'images', plugins: [ new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days }), ], }));
// API calls - network first with cache fallbackregisterRoute( ({ url }) => url.pathname.startsWith('/api/'), new StaleWhileRevalidate({ cacheName: 'api-cache', }));Server-Side Caching
Cache expensive database queries and API responses on the server to reduce processing time and database load.
Redis Caching Example
// Node.js with Redisconst redis = require('redis');const client = redis.createClient();
async function getUser(userId) { const cacheKey = `user:${userId}`;
// Try to get from cache first const cached = await client.get(cacheKey); if (cached) { console.log('Cache hit'); return JSON.parse(cached); }
// Cache miss - fetch from database console.log('Cache miss'); const user = await db.users.findById(userId);
// Store in cache for 1 hour await client.setEx(cacheKey, 3600, JSON.stringify(user));
return user;}
// Invalidate cache on updateasync function updateUser(userId, data) { await db.users.update(userId, data); await client.del(`user:${userId}`); // Clear cache}Next.js Server-Side Caching
// Next.js API route with cachingexport async function GET(request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id');
// Cache for 1 hour with revalidation const data = await fetch(`https://api.example.com/data/${id}`, { next: { revalidate: 3600 } });
return Response.json(await data.json());}
// Static page with ISR (Incremental Static Regeneration)export const revalidate = 3600; // Revalidate every hour
export default async function Page() { const data = await fetchData(); return <div>{data}</div>;}Cache Invalidation Strategies
"There are only two hard things in Computer Science: cache invalidation and naming things." - Phil Karlton
Time-Based Expiration (TTL)
Set expiration times for cached content. Simple but may serve stale data.
# Cache for specific durationCache-Control: max-age=3600, public # 1 hourCache-Control: max-age=86400, public # 1 dayCache-Control: max-age=31536000, immutable # 1 year (versioned assets)Event-Based Invalidation
Purge cache when content changes. More accurate but requires infrastructure.
// Purge CDN cache on content updateasync function updateArticle(articleId, data) { await db.articles.update(articleId, data);
// Purge CDN cache for this article await cdn.purge(`/articles/${articleId}`); await cdn.purge('/articles/list'); // Also purge list page}Stale-While-Revalidate
Serve cached content while fetching fresh data in background. Best UX.
# Serve stale for up to 3600s while revalidatingCache-Control: max-age=600, stale-while-revalidate=3600Caching Implementation Checklist
Optimize Your Caching Strategy
Analyze your website's caching configuration and get AI-powered recommendations to improve cache efficiency and reduce load times.
Analyze Caching Performance