Offline Does Not Mean Durable: Auditing Browser Storage
IndexedDB, Cache Storage, and OPFS can retain data for a long time without guaranteeing it forever. Audit quotas, persistence, eviction, synchronization, and recovery.
A field-service app stores forms offline. An editorial system keeps drafts locally until connectivity returns. A SaaS product places files or search indexes in IndexedDB or the Origin Private File System. In testing, everything survives reloads and even browser restarts.
Then a user reports: “My draft is gone.”
The instinctive response is often: “But we store it in IndexedDB.”
That is the misunderstanding. Browser storage is not automatically durable application storage. The web platform distinguishes between best-effort and persistent storage. Best-effort is the normal mode: data can survive across sessions, but it is not guaranteed against quota limits, storage pressure, browser policy, or explicit deletion by the user.
That is perfectly acceptable for disposable caches. For the only copy of a contract draft, an unsynchronized order, or an offline work report, it becomes an architecture question.
Myth: If it survives a reload, it is persistent
localStorage, IndexedDB, Cache Storage, and OPFS can retain data across reloads and browser restarts. That proves only that the data has not been removed yet.
The WHATWG Storage Standard defines a common storage architecture, quota estimates, and a mechanism for requesting persistent storage. By default, however, an origin normally operates with best-effort storage. Under storage pressure, the browser may evict non-persistent origin data.
Three properties should therefore be considered separately:
- Session-durable: data may survive tabs and restarts.
- Persistent storage: the browser has granted stronger protection against automatic eviction.
- Server-backed: another copy exists outside this browser profile.
Those are not the same guarantee.
The storage budget is bigger than IndexedDB
One origin may simultaneously use IndexedDB for records, Cache Storage for responses and offline assets, OPFS for files, localStorage for small key-value data, and service-worker-related state.
Operationally, that means an application does not consume quota only with business data. A large offline cache, thumbnails, or downloaded files can compete with important local records for finite device storage. “Our IndexedDB is only 30 MB” is therefore not a complete capacity analysis.
Red flag 1: Nobody knows current usage or quota
navigator.storage.estimate() lets an application request an estimate of current usage and quota. The word estimate matters: these values are not a permanent allocation. Quotas vary by browser, device, free disk space, and browsing mode, and can change.
The API is still useful. An offline app that starts a large download without checking whether roughly enough space is available turns a predictable condition into a surprise failure.
Useful product logic can check quota before large writes, define warning thresholds, clear expendable cache data first, and explain when an offline package cannot be stored completely.
Red flag 2: QuotaExceededError exists only in documentation
When a write exceeds available quota, storage operations can fail. MDN documents QuotaExceededError for technologies such as IndexedDB, Cache Storage, and OPFS.
A resilient client treats that as a real operational state: writes have explicit error handling, a record remains visibly unsaved or unsynchronized, the UI does not report success before the write completes, and expendable data can be removed deliberately.
A dangerous pattern is a button turning green, a dialog closing, and an asynchronous storage write failing in the background. Technically, an exception occurred. From the user's perspective, the product broke a promise.
Red flag 3: Critical data exists only locally
Browser storage can be the right foundation for a deliberately local-first application. But local-first still needs an answer for loss, conflicts, and recovery.
For many SaaS products, a useful rule is: data whose loss would hurt the business should not remain indefinitely as the only copy inside one browser profile.
A draft can originate offline. Once connectivity returns, a defined synchronization path should take over. Depending on the product, that includes stable local identifiers, server versions, retries, conflict handling, idempotent synchronization, and visible sync status.
A green “Saved” indicator should distinguish “saved locally” from “backed up to the server.” Those states should not look identical when their risk is different.
Red flag 4: Persistence is never checked
The Storage API provides navigator.storage.persisted() to check whether an origin already has persistent storage. navigator.storage.persist() can request it.
The request is not a command. Browsers apply their own rules when deciding whether to grant persistence, so the application must remain correct when the result is false.
Persistent storage also should not be requested reflexively on the first page view. A more meaningful moment is when the user enables a feature whose local data genuinely matters, such as “Make available offline” or saving work that has not yet synchronized.
Red flag 5: Private browsing has never been tested
Private or incognito browsing is a separate test case. Browsers may apply different quotas there, and locally stored data is generally removed when the private session ends.
An app that works perfectly offline in a normal profile can receive less storage in private mode, handle persistence differently, and lose all local data when the session ends. That is not necessarily a browser bug. It becomes a product bug when the interface still promises durable offline availability.
Cache data and irreplaceable data need different priority
A downloaded thumbnail can be regenerated. An unsynchronized draft may not be. Yet both are often put into the same implicit risk category: “stored locally.”
A better model classifies data:
- Regenerable: bundles, images, API caches, search indexes. Loss costs time or bandwidth, not business records.
- Synchronized: local copies of data already safely stored on the server.
- Not yet synchronized: drafts, inputs, recordings, or work states that exist only on this device. This class deserves the highest priority and rapid synchronization.
- Intentionally local: data that deliberately never goes to the server. If loss is unacceptable, the product needs an explicit export, backup, or recovery strategy.
This classification is often more useful for architecture than “IndexedDB or OPFS?”
Browsers differ — so a fixed GB number is the wrong requirement
WebKit documents a device-capacity-based quota model and conditions under which best-effort origins can be evicted. Chrome and other browsers apply their own quota policies. MDN documents these differences across implementations.
A robust requirement is therefore not: “We always have 5 GB.” It is: we can estimate the current quota, detect failed writes, free expendable data, recover critical data, and degrade offline features clearly when storage is unavailable.
What a useful storage audit tests
A real audit goes beyond “Does IndexedDB exist?” It checks:
- Data inventory: What lives in IndexedDB, Cache Storage, OPFS, and
localStorage? What is regenerable, synchronized, or unique? - Quota and growth: How much space is consumed after a day, a month, or a hundred offline records?
- Failure paths: What happens on
QuotaExceededError, interrupted writes, or low disk space? Does the UI remain truthful? - Persistence state: Is
persisted()checked? Is there a meaningful moment forpersist()? Does the product still work if persistence is denied? - Synchronization: What happens after long offline use, multiple devices, or server-side changes?
- Deletion and recovery: Does the app recover cleanly after Clear site data, a profile change, or eviction?
- Migrations: Does a new release work with an old, populated IndexedDB rather than only with a clean developer profile?
Test loss deliberately
The most valuable browser-storage test is not “reload and the data is still there.” Test almost-full storage, a large offline dataset, persistence granted and denied, private browsing, deleted site data, offline writes followed by reconnect, and a new release against an existing local database.
The goal is not to prevent every browser storage decision. You cannot. The goal is to avoid turning storage loss into application chaos.
What Website-Pflichtencheck would inspect
For a web app using local storage, Website-Pflichtencheck would review the actual storage mechanisms, data classes, usage and quota, persistence state, write error handling, offline synchronization, cache cleanup, private browsing behavior, schema upgrades, and recovery paths.
This matters especially for products that promise “works offline,” “drafts are saved automatically,” or “we will sync later.” Those statements are not merely UX copy. They imply a technical durability guarantee that should be testable.
Browser storage is powerful. IndexedDB, Cache Storage, and OPFS enable very capable web applications. But available locally does not automatically mean durably backed up. Model that boundary deliberately and users can tell what exists only on this device, what has synchronized, and what can be recovered after failure.