Cache storage eviction rules determine whether your PWA survives storage pressure
Your PWA's cached content can be evicted. Here's how browsers decide, and how to make your app resilient.
Why This Matters
Browsers maintain separate storage budgets and eviction policies. When storage pressure hits—low disk space, user action, periodic cleanup—your PWA's cached assets can be evicted without warning. If your app requires cached content to work offline and that content gets evicted silently, your offline functionality breaks. Users will blame your app, not understand that browser storage policies executed.
The Cache Storage Layer
Cache Storage is where your service worker stores responses:
const cache = await caches.open('v1');
await cache.addAll([
'/',
'/styles.css',
'/scripts.js',
'/images/logo.png'
]);This resides in browser storage space alongside IndexedDB, Web Storage (localStorage/sessionStorage), and other APIs.
Browser Eviction Policies
Chrome:
- Uses "eviction pressure" model
- Least recently used (LRU) eviction within same-origin storage
- Evicts entire origin if storage limit reached
- Eviction threshold: roughly 50-60% of disk space across all browser origins
Firefox:
- Per-origin eviction with LRU logic
- More aggressive on low disk space
- Can evict caches even for active PWAs
- User can manually clear site data from settings
Safari:
- Known for aggressive cache eviction, especially on iOS
- Periodic cleanup removes old caches automatically
- iStorage gets evicted more aggressively than Cache Storage
- TWA packages (for Android) have better persistence due to native packaging
The Storage Pressure Triggers
Browsers evict for these reasons:
- Low disk space - When the device has less than ~100MB free
- User clears site data - Manual action in browser settings
- Periodic maintenance - Safari and Firefox clean up old caches
- Storage quota exceeded - Your app tries to store more than allowed
You can't prevent triggers 1, 3, and 4. You can only design for resilience.
Detection and Estimation
Storage Manager API:
const estimate = await navigator.storage.estimate();
console.log(`Quota: ${estimate.quota} bytes`);
console.log(`Usage: ${estimate.usage} bytes`);
console.log(`Percent used: ${Math.round(estimate.usage / estimate.quota * 100)}%`);Persistence grants:
navigator.storage.persist().then((persistent) => {
if (persistent) {
console.log('Storage persistence granted');
} else {
console.log('Persistence denied - data may be evicted');
}
});Even with persistence granted, eviction can still happen—it just makes it less likely.
Survival Strategies
Keep your cache lean:
- Only cache what's essential for offline functionality
- Use ephemeral caching (don't persist) for content that can be re-fetched
- Implement cache versioning to remove old versions
// Delete old cache versions
const cacheNames = await caches.keys();
for (const name of cacheNames) {
if (name !== currentCacheName) {
await caches.delete(name);
}
}Detect and recover from eviction:
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(async (cacheNames) => {
// Check if our expected cache exists
const hasCache = cacheNames.includes(currentCacheName);
if (!hasCache) {
// Cache was evicted, re-populate it
return caches.open(currentCacheName).then((cache) => {
return cache.addAll(essentialAssets);
});
}
})
);
});Implement fallback to network:
When cached content is missing, fetch from network and update the cache:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
if (response) {
return response;
}
return fetch(event.request).then((response) => {
// Cache the new response for next time
return caches.open(currentCacheName).then((cache) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});The Eviction Rubric
Use this checklist to assess risk:
| Factor | Low Risk | Medium Risk | High Risk | |--------|----------|-------------|-----------| | Cache size | < 10MB | 10-50MB | > 50MB | | Content type | Static assets only | Mixed content | User-generated ephemeral data | | User engagement | Daily active | Weekly active | Monthly active | | Platform | Android (native packages) | Desktop | iOS Safari |
High risk + unclear fallback = broken offline experience for users.
Monitor Cache Health
Track these metrics in production:
- Cache hit rate (how often fetches are served from cache)
- Cache miss rate for essential assets
- Storage quota percentage over time
- Eviction detection (when expected caches disappear)
- Re-cache success rates
Alert developers when:
- Cache hit rate drops below 80%
- Storage usage consistently exceeds 70% of quota
- Eviction is detected for essential caches
For Developers
Cache eviction is inevitable. Design your PWA to handle it gracefully. Offline reliability isn't about preventing eviction—it's detecting it and recovering transparently.
Treat cache storage as volatile. Implement re-caching logic on service worker activation. Design pages to work when cached content is missing (show loading states, fetch on demand, provide fallback UI).
Next Steps
- Implement cache eviction detection in your install/activate handlers
- Add Storage Manager API telemetry to your error tracking
- Design cache-critical assets vs. cache-optional assets
- Test eviction scenarios by manually clearing cache and observing behavior
- Document your cache recovery plan for on-call engineers
Your PWA should survive cache eviction, not die from it.