HTTP Caching Headers
HTTP Caching Headers
Control how browsers cache responses.
Cache-Control
# Cache for 1 year (immutable assets)
Cache-Control: public, max-age=31536000, immutable
# Cache for 1 hour
Cache-Control: public, max-age=3600
# No caching
Cache-Control: no-cache, no-store, must-revalidate
# Revalidate before using cache
Cache-Control: no-cache
ETag and Last-Modified
# Server response
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
# Client conditional request
If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT
# Server response if not modified
304 Not Modified
Vary Header
# Cache different versions based on headers
Vary: Accept-Encoding
Vary: Accept-Language
Vary: Authorization
Content Hashing
// Webpack config
module.exports = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
},
};
// HTML template
<script src="main.a1b2c3d4.js"></script>
<link rel="stylesheet" href="styles.e5f6g7h8.css">
Server Configuration
// Express.js
app.use(express.static('public', {
maxAge: '1y',
immutable: true,
etag: true,
lastModified: true,
}));
// Nginx
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Service Worker Caching
Service Worker Caching
Cache resources for offline use and faster loading.
Basic Service Worker
// sw.js
const CACHE_NAME = 'v1';
const ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/offline.html',
];
// Install event
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS);
})
);
});
// Fetch event
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
// Activate event
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
});
Cache Strategies
// Cache First (for static assets)
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
const response = await fetch(request);
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
}
// Network First (for API calls)
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open(CACHE_NAME);
cache.put(request, response.clone());
return response;
} catch (error) {
const cached = await caches.match(request);
return cached || new Response('Offline', { status: 503 });
}
}
// Stale While Revalidate
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then((response) => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
Workbox
// Using Workbox
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Cache images
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
],
})
);
// Cache API responses
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
],
})
);
Cache Strategies
Cache Strategies
Choose the right caching strategy for different resources.
Strategy Matrix
| Resource | Strategy | Rationale |
|---|---|---|
| HTML | Network First | Always get latest |
| CSS/JS | Cache First + Revalidate | Fast loading, occasional updates |
| Images | Cache First | Rarely change |
| API Data | Stale While Revalidate | Show cache, update in background |
| Fonts | Cache First | Very stable |
Implementation
// sw.js with different strategies
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// HTML: Network First
if (request.destination === 'document') {
event.respondWith(networkFirst(request));
return;
}
// Static Assets: Cache First
if (request.destination === 'style' || request.destination === 'script') {
event.respondWith(cacheFirst(request));
return;
}
// Images: Cache First
if (request.destination === 'image') {
event.respondWith(cacheFirst(request));
return;
}
// API: Stale While Revalidate
if (url.pathname.startsWith('/api/')) {
event.respondWith(staleWhileRevalidate(request));
return;
}
// Default: Network First
event.respondWith(networkFirst(request));
});
Cache Invalidation
// Versioned caches
const CACHE_VERSION = 'v2';
const CACHE_NAME = `cache-${CACHE_VERSION}`;
// On activate, delete old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name.startsWith('cache-') && name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
})
);
});
Offline Fallback
// Offline fallback page
const OFFLINE_URL = '/offline.html';
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(OFFLINE_URL);
})
);
}
});
Best Practices
- Version your caches for easy invalidation
- Use different strategies for different resource types
- Implement offline fallback for navigation
- Clean up old caches in activate event
- Use Workbox for production service workers
Practice Problems
Create a reusable React component implementing Browser Caching. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Browser Caching using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Browser Caching for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What does Cache-Control: immutable mean?
2. When should you use network-first strategy?
3. What is the primary purpose of Browser Caching?
4. What is a common mistake when implementing Browser Caching?
Flashcards
Question
What are the main HTTP caching headers?
Click to reveal answer
Answer
Cache-Control, ETag, Last-Modified, and Vary control browser caching behavior.
Question
When should you use cache-first vs network-first?
Click to reveal answer
Answer
Cache-first for static assets (images, CSS); network-first for dynamic content (HTML, API).
Question
What is a service worker?
Click to reveal answer
Answer
A background script enabling caching, offline support, and background sync.
Question
Why version your caches?
Click to reveal answer
Answer
To easily invalidate and delete old caches when updating service workers.
Question
What is Browser Caching?
Click to reveal answer
Answer
Browser Caching is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Use content hashing for immutable assets
- 2.Different resources need different caching strategies
- 3.Service workers enable offline support and better caching
- 4.Version caches for easy invalidation
- 5.Use Workbox for production service workers
Interview Tips
- •Explain when to use cache-first vs network-first
- •Discuss service worker caching strategies
- •Know how to configure HTTP caching headers
Cheat Sheet
Browser Caching Cheat Sheet
HTTP Headers
- Cache-Control: max-age, immutable, no-cache
- ETag: Conditional requests
- Vary: Different versions by header
Service Worker Strategies
- Cache First: Static assets
- Network First: HTML, API
- Stale While Revalidate: Semi-dynamic
Workbox
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({ cacheName: 'images' })
);