Debugging storage eviction in progressive web apps
Offline-first progressive web apps frequently experience silent data loss following device reboots or OS-level memory pressure. Developers encounter QuotaExceededError during write operations or discover previously cached assets missing without explicit delete() calls. This behavior stems directly from default browser storage classifications under Browser Storage Fundamentals & Quotas.
Primary Symptoms:
- IndexedDB databases vanish after a full browser restart
- Service Worker caches return empty responses during offline routing
- No explicit error is thrown during the background eviction event
Root Cause: Best-Effort Storage Classification
Modern browsers classify origin storage as best-effort by default. When system disk space falls below critical thresholds, the operating system triggers Storage Quotas & Eviction Policies to reclaim space. Non-persistent origins are prioritized for eviction, bypassing standard window.onerror or Promise.catch handlers.
Technical Factors Driving Eviction:
- Missing
navigator.storage.persist()invocation during initialization - Lack of proactive quota monitoring via
navigator.storage.estimate() - Cross-browser differences in eviction thresholds (Chromium vs. WebKit vs. Gecko)
Step-by-Step Fix: Enforce Persistent Storage & Handle Eviction
Implement the following workflow to secure offline state and gracefully handle storage pressure.
1. Request Persistent Storage Permission
Invoke navigator.storage.persist() during app initialization. Handle promise rejection to fallback gracefully. Browsers may prompt the user or auto-grant based on engagement metrics (PWA installed, frequent visits).
2. Monitor Quota Usage Proactively
Poll navigator.storage.estimate() periodically. Trigger cache pruning when usage exceeds 80% of the allocated quota to prevent hard QuotaExceededError failures during critical writes.
3. Implement Eviction Fallback & Integrity Checks
Wrap IndexedDB transactions in explicit try/catch blocks. Add periodic health checks to detect missing databases and trigger network re-sync.
Production-Ready Implementation
/**
* Secure offline storage initialization with persistence, quota monitoring,
* and eviction-safe fallbacks.
*/
async function secureOfflineStorage() {
try {
// Step 1: Request persistent storage
const isPersisted = await navigator.storage.persist();
if (!isPersisted) {
console.warn('Storage not persisted; data may be evicted under OS memory pressure.');
}
// Step 2: Estimate quota & trigger proactive pruning
const { usage, quota } = await navigator.storage.estimate();
const usagePercent = (usage / quota) * 100;
if (usagePercent > 80) {
await cleanupOldCaches();
}
// Step 3: Verify IndexedDB integrity
await verifyDatabaseIntegrity();
return { persisted: isPersisted, usagePercent, quota, usage };
} catch (err) {
// Explicit error routing for production logging
if (err.name === 'QuotaExceededError') {
console.error('Storage quota exceeded during initialization. Triggering emergency cleanup.');
await emergencyCacheClear();
} else {
console.error('Storage persistence failed:', err.name, err.message);
}
throw new Error('Offline storage initialization failed');
}
}
async function cleanupOldCaches() {
// Implementation: Iterate caches, delete oldest by timestamp, retain critical shell assets
const cacheNames = await caches.keys();
// ... pruning logic ...
}
async function verifyDatabaseIntegrity() {
// Implementation: Open DB, ping a version table, trigger network sync if missing
}
Validation: Verify Persistence and Eviction Resilience
After deployment, validate storage behavior using the following checklist and browser-native diagnostics.
Validation Checklist:
- [ ] Confirm
navigator.storage.persisted()resolves totruein DevTools Console - [ ] Simulate low-disk conditions via Chrome DevTools > Application > Storage > Clear site data (partial)
- [ ] Verify IndexedDB databases survive a full browser restart
- [ ] Monitor
QuotaExceededErrorfrequency in production error tracking (Sentry, LogRocket, etc.)
Debugging Commands & Tools:
- Chromium:
chrome://storage-internals/(View origin persistence flags and quota allocation) - Firefox:
about:debugging#workers(Inspect Service Worker cache state and storage limits) - Console Diagnostics:
navigator.storage.estimate().then(console.log)(Returns{usage, quota}in bytes)