Cache API: Offline Storage for PWA
Learn: Cache API: Offline Storage for PWA
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
Cache API: Offline Storage for PWA
Problem
Modern web applications need to work reliably regardless of network conditions. Users expect apps to load instantly, function offline, and reduce bandwidth consumption. Traditional approaches fail when connectivity drops, creating poor user experiences and wasted data usage.
Solution
The Cache API provides a persistent, asynchronous storage mechanism specifically designed for Progressive Web Apps (PWAs). Combined with Service Workers, it enables:
- Offline functionality - Serve cached content when network unavailable
- Performance optimization - Instant asset delivery from local storage
- Bandwidth reduction - Avoid redundant network requests
- Resilience - Graceful degradation when connectivity fails
The Cache API stores HTTP responses as key-value pairs, allowing fine-grained control over what gets cached and when.
Code
1. Basic Service Worker Setup
// service-worker.js
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
];
// Install event - cache essential assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
2. Cache-First Strategy
// Serve from cache, fallback to network
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request)
.then(response => {
// Return cached response if available
if (response) return response;
// Otherwise fetch from network
return fetch(event.request)
.then(response => {
// Don't cache non-successful responses
if (!response || response.status !== 200) {
return response;
}
// Clone and cache the response
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
.catch(() => {
// Return offline fallback page
return caches.match('/offline.html');
})
);
});
3. Network-First Strategy
// Try network first, fallback to cache
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// Cache successful responses
if (response && response.status === 200) {
const responseClone = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseClone);
});
}
return response;
})
.catch(() => {
// Fall back to cache on network failure
return caches.match(event.request)
.then(response => response || caches.match('/offline.html'));
})
);
});
4. Stale-While-Revalidate Strategy
// Serve cached content immediately, update in background
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME)
.then(cache => {
return cache.match(event.request)
.then(response => {
// Serve cached response immediately
const fetchPromise = fetch(event.request)
.then(networkResponse => {
// Update cache with fresh response
if (networkResponse && networkResponse.status === 200) {
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
})
.catch(() => response);
return response || fetchPromise;
});
})
);
});
5. Register Service Worker
// main.js - Client-side registration
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered:', registration);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
// Listen for updates
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('New Service Worker activated');
});
6. Advanced Cache Management
// Utility functions for cache management
const CacheManager = {
// Clear specific cache
clearCache: async (cacheName) => {
return caches.delete(cacheName);
},
// Clear all caches
clearAllCaches: async () => {
const cacheNames = await caches.keys();
return Promise.all(cacheNames.map(name => caches.delete(name)));
},
// Get cache size
getCacheSize: async () => {
const cacheNames = await caches.keys();
let totalSize = 0;
for (const name of cacheNames) {
const cache = await caches.open(name);
const keys = await cache.keys();
for (const request of keys) {
const response = await cache.match(request);
const blob = await response.blob();
totalSize += blob.size;
}
}
return totalSize;
},
// Limit cache size
limitCacheSize: async (cacheName, maxItems) => {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxItems) {
await cache.delete(keys[0]);
return CacheManager.limitCacheSize(cacheName, maxItems);
}
},
// Precache with timeout
precacheWithTimeout: async (urls, timeout = 5000) => {
const cache = await caches.open(CACHE_NAME);
return Promise.all(
urls.map(url => {
return Promise.race([
cache.add(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]).catch(err => console.warn(`Failed to cache ${url}:`, err))
})
);
}
};
Tips
1. Versioning Strategy
Always version your cache names (app-cache-v1, app-cache-v2). This allows simultaneous caching of multiple versions and clean migration paths.
2. Choose the Right Strategy
- Cache-First: Static assets, images, fonts
- Network-First: API calls, dynamic content
- Stale-While-Revalidate: Balance between freshness and performance
3. Handle Cache Invalidation
Implement cache busting by appending query parameters to asset URLs or using content hashes in filenames.
4. Monitor Cache Size
Browsers limit cache storage (typically 50% of available disk space). Implement size limits and cleanup routines to prevent quota exceeded errors.
5. Test Offline Functionality
Use DevTools to simulate offline mode. Check Network tab → Throttling → Offline to verify fallback behavior.
6. Exclude Sensitive Data
Never cache authentication tokens, personal information, or sensitive API responses. Use appropriate cache headers.
7. Update Strategy
Implement background sync to update caches periodically. Notify users when updates are available.
8. Cross-Origin Requests
Cache API respects CORS. Ensure cross-origin resources include appropriate headers or use no-cors mode carefully.
9. Debug with DevTools
- Application tab → Cache Storage shows all cached entries
- Network tab shows cache hits (from ServiceWorker)
- Console logs help track cache operations
10. Progressive Enhancement
Always provide fallback content. Ensure core functionality works even if caching fails or is unavailable.