Caching and Offline in Progressive Web Apps¶
A Progressive Web App works offline because it controls where every byte comes from: a service worker intercepts requests and answers them from Cache Storage, IndexedDB or the Origin Private File System instead of the network. Those programmable stores sit alongside browser-managed caches you do not control directly (the per-document memory cache, the back/forward cache and the HTTP cache), and each layer has different lifetimes, partitioning rules, quotas and failure modes. This page maps every layer, shows how they stack on a single request, and tells you which one to use for each kind of data before you dive into the detailed pages of this section.
Key takeaways
- There are two kinds of layer: browser-managed caches (memory cache, back/forward cache, HTTP cache) that follow HTTP rules and heuristics, and script-managed storage (Cache Storage, IndexedDB, OPFS, Web Storage) that keeps exactly what you put there until you delete it or the browser evicts the whole origin.
- Cache Storage stores full
Request/Responsepairs and is the natural home for anything you will hand toevent.respondWith(). It ignoresCache-Control, so freshness and expiration are your job. - IndexedDB is the source of truth for structured application data and outbox queues. OPFS is for large files and high-throughput binary I/O, including SQLite databases.
localStorageis synchronous, string-only, capped at about 5 MiB and unavailable in workers, so a service worker cannot read it. Keep it for tiny UI preferences.- Cache Storage, IndexedDB, OPFS and service worker registrations share one quota per storage key, and eviction under storage pressure removes all of that data at once unless you have persistent storage.
- A
fetch()from a service worker still goes through the HTTP cache, so the two layers interact. Stale HTTP-cached files that end up in a precache are one of the most common offline bugs.
The caching and storage layers at a glance¶
Browsers expose at least eight places where a response or a piece of application state can live. The table summarizes who controls each one, how long data survives, and what it is good for. The subsections that follow explain each layer in more depth. Where a layer has its own page in this section, the subsection is a summary and links there.
| Layer | Controlled by | Stores | Lifetime | Readable from workers | Honors HTTP caching headers |
|---|---|---|---|---|---|
| Memory cache | Browser (per document) | Decoded images, preloads, recently used subresources | Current document | No (not an API) | Mostly no |
| Back/forward cache | Browser | Entire page snapshot incl. JS heap | Minutes, in memory | No (not an API) | no-store affects eligibility |
| HTTP cache | Server headers + browser | HTTP responses | Until evicted or expired | Indirectly via fetch() | Yes, per RFC 9111 |
| Cache Storage | Your script | Request → Response pairs | Until deleted or origin evicted | Yes | No |
| IndexedDB | Your script | Structured-clonable values, Blobs | Until deleted or origin evicted | Yes | Not applicable |
| OPFS | Your script | Files and directories | Until deleted or origin evicted | Yes (sync handles in dedicated workers) | Not applicable |
localStorage / sessionStorage | Your script | String key/value pairs | Persistent / per tab session | No | Not applicable |
| Cookies | Server and script | Small strings sent with requests | Per Expires/Max-Age | Only via the Cookie Store API, where supported | Not applicable |
Memory cache: per-document reuse you do not control¶
Every engine keeps recently used subresources in memory for the lifetime of a document, so that the second <img src="/logo.svg"> on a page or a script fetched by <link rel="preload"> does not trigger a second fetch. The HTML Standard describes parts of this behavior explicitly (the list of available images and the map of preloaded resources that a matching fetch consumes), and engines add their own layers such as Blink's in-memory resource cache.
Two properties matter for PWAs. First, a hit in this layer is served before the request reaches the service worker, so it does not dispatch a fetch event. The original load already went through your service worker, so this rarely causes bugs, but it explains why a fetch handler can see fewer requests than the page makes. Second, the memory cache does not apply HTTP freshness rules the way the HTTP cache does. Yoav Weiss's article A Tale of Four Caches (linked below) remains the clearest description of this layer, although the fourth cache it describes, the HTTP/2 push cache, is gone: Chrome disabled HTTP/2 Server Push by default in Chrome 106.
Back/forward cache: whole-page snapshots¶
The back/forward cache (bfcache) keeps a complete, frozen copy of a page (DOM, JavaScript heap, scroll position) in memory after the user navigates away, and restores it instantly on Back or Forward. It is not an API and it caches no individual responses, but it is the fastest "cache" a PWA can hit, and it interacts with your storage code:
- Restores fire
pageshowwithevent.persisted === trueinstead of a freshload. Any data you read from IndexedDB or Cache Storage at startup can be stale after a restore, so refresh it in apageshowhandler. - Use
pagehideinstead ofunloadto flush state. Anunloadlistener can make a page ineligible for bfcache in some browsers, and it does not run reliably on mobile. Cache-Control: no-storeon the HTML historically made pages ineligible. Chrome finished rolling out bfcache forno-storepages to all users in March and April 2025. It evicts such pages when cookies or other authentication state change and keeps them for 3 minutes rather than the usual 10.- Open connections (WebSocket, WebRTC) and certain in-flight work can still block the cache. Chromium's
notRestoredReasonson the navigation timing entry (Chrome 125 and later) tells you why a page was not restored.
Details and measurement belong to the performance section: see Loading Performance and Measuring Performance.
HTTP cache: the browser's network cache¶
The HTTP cache stores responses according to Cache-Control, Expires, ETag, Last-Modified and Vary, as defined by RFC 9111. It is shared by every request the browser makes for your origin, including fetch() calls made inside your service worker and the navigation preload request. It is also partitioned: Chrome keys it by top-level site and frame site since Chrome 86, Firefox partitions it by top-level site since Firefox 85, and Safari has long partitioned by top-level site. A CDN-hosted library cached on one site is therefore not reused on another.
The No-Vary-Search response header lets a response declare which query parameters do not change its content, so /list?utm_source=a and /list can share one HTTP cache entry. The HTTP cache honors it in Chrome 141 and later and in Firefox 154 and later. Safari does not support it as of September 2026. Cache Storage has no equivalent beyond the all-or-nothing ignoreSearch option, which is one of many semantic differences covered in HTTP Caching & Service Workers.
Service worker and Cache Storage: the programmable layer¶
Cache Storage (self.caches) is an origin-scoped set of named caches, each an ordered list of Request/Response pairs. It is exposed in windows, dedicated and shared workers, and service workers, only in secure contexts. The spec is explicit that these caches are not part of the HTTP cache: entries are never updated, revalidated or expired unless your code does it. A service worker combines this store with the fetch event to implement cache-first, network-first and stale-while-revalidate behavior.
Cache Storage matches on URL, method and Vary. It can hold opaque cross-origin responses, which Chromium pads for quota purposes by a pseudo-random amount between 0 and about 14 MiB each (about 7 MiB on average). addAll() is atomic, and it rejects on any non-2xx response. The full API surface, the matching algorithm, and working code for expiration, LRU trimming and size measurement are on the Cache Storage API page. How to use it for specific request types is covered in Caching Strategies and Precaching & Runtime Caching.
Chromium (since Chrome 123) and Safari (since Safari 27) also support the Service Worker Static Routing API, which lets a worker declare at install time that certain requests should be answered with source: "cache" (optionally a specific cacheName) without starting the worker at all. See Static Routing API.
IndexedDB: structured data and the offline source of truth¶
IndexedDB is a transactional object store with indexes, cursors and key ranges, available in windows and all worker types, including service workers. It stores anything the structured clone algorithm supports, including Blob, File, ArrayBuffer, Map and Date. For an offline-first app it is where the canonical copy of user data lives: records synced from the server, drafts, and the outbox of mutations waiting for Background Sync. It is also the right place for metadata about your Cache Storage entries (insertion time, last access, byte size), because Cache Storage itself cannot record any. See IndexedDB and Offline-First Data & Sync.
Origin Private File System: files and high-throughput binary I/O¶
The Origin Private File System (OPFS), reached through navigator.storage.getDirectory(), is a sandboxed file system that is invisible to the user and scoped to your origin. It is supported in all three engines (Chrome 86, Firefox 111, Safari 15.2 for getDirectory()). In dedicated workers, FileSystemFileHandle.createSyncAccessHandle() gives synchronous, in-place reads and writes, which is what makes SQLite-in-WebAssembly and other database engines practical in the browser. Use it for large media, document files the app edits in place, and database files. OPFS counts against the same quota as Cache Storage and IndexedDB. See Origin Private File System. For files the user can see and pick, use the separate File System Access API.
localStorage and sessionStorage: why they are not offline storage¶
Web Storage looks convenient, but it is a poor fit for PWA data:
- It is synchronous. Every
getItem/setItemcan block the main thread on disk I/O, and the first access may load the whole store. - It is window-only.
localStoragedoes not exist inWorkerGlobalScope, so your service worker cannot read a token or a setting you put there. - It stores strings only, and MDN documents the limit as 5 MiB for
localStorageplus 5 MiB forsessionStorageper origin. Exceeding it throwsQuotaExceededError. - It has no transactions, so concurrent tabs can overwrite each other's read-modify-write updates. The
storageevent fires only in other documents. - It is subject to the same eviction rules as other script-writable storage, including Safari's seven-day cap described below.
Keep localStorage for tiny, non-critical UI preferences such as a theme or a dismissed banner, and read those only on the main thread. Anything the service worker needs goes in IndexedDB.
Cookies are not an application data store¶
Cookies are sent with every matching request, are limited to about 4 KB each, and are partitioned or blocked in third-party contexts. They are the right tool for session identifiers and server-read flags, not for offline data. Note that Cache Storage does not know about cookies at all: two users who share a device and the same cached URL get the same cached response. Clear user-specific caches on logout.
How the layers stack on a single request¶
The sequence below follows one subresource request from a page controlled by a service worker. It includes the static router and both hit and miss paths through Cache Storage and the HTTP cache. It also shows the offline branch that makes the request succeed without a network.
sequenceDiagram
autonumber
participant Page
participant Mem as Memory cache
participant Router as Static router
participant SW as Fetch handler
participant CS as Cache Storage
participant HTTP as HTTP cache
participant Net as Network
Page->>Mem: GET /app.js
alt reused by this document
Mem-->>Page: in-memory response, no fetch event
else not in memory
Page->>Router: request enters the service worker layer
alt route source is cache
Router->>CS: lookup without starting the worker
CS-->>Page: cached response
else fetch-event route or no routes
Router->>SW: dispatch fetch event
SW->>CS: caches.match(request)
alt cache hit
CS-->>SW: Response
SW-->>Page: respondWith(cached)
else cache miss
SW->>HTTP: fetch(request)
alt fresh in HTTP cache
HTTP-->>SW: stored response
else stale or absent
HTTP->>Net: conditional or full request
alt online
Net-->>HTTP: 200 or 304
HTTP-->>SW: response
SW->>CS: cache.put(request, clone)
else offline
Net--xHTTP: network error
HTTP--xSW: fetch() rejects with TypeError
SW->>CS: match offline fallback
end
end
SW-->>Page: response or fallback
end
end
end Several details in this flow cause real-world bugs:
- Memory cache hits skip the fetch handler. Instrumentation that counts requests in the service worker undercounts repeat subresource loads within a document.
- The static router runs first. With
addRoutes(), a matchingcacheroute answers from Cache Storage without waking the worker. If the entry is missing, the browser goes straight to the network, not to your fetch handler, so route only URLs you know are cached. - Cache lookups happen in your code, in your order. A
caches.match()without acacheNamewalks every cache in creation order and returns the first hit. Two caches holding different versions of the same URL can therefore serve the older one. - Your
fetch()goes through the HTTP cache. If/app.jsis served withCache-Control: max-age=31536000but is not revisioned in its URL, a precache step can copy a year-old HTTP-cached copy into Cache Storage. Request it withcache: "reload"orcache: "no-cache", or use hashed file names. See HTTP Caching & Service Workers. - Offline is a rejected promise, not a status code. When the network is unreachable,
fetch()rejects with aTypeError. A 404 or 500 from a reachable server resolves normally, and your strategy must decide whether an error response should be cached or replaced by a fallback. See Offline UX & Fallbacks. - Navigations are special. The HTML request for a page uses
mode: "navigate"andredirect: "manual". A cached response that was the result of a redirect (response.redirected === true) triggers a network error when used for a navigation. Cache Storage API shows how to strip that flag.
What happens in the service worker's absence¶
Before a service worker has installed and claimed the page (the first visit), or when the user force-reloads with Shift and the reload button, requests bypass the service worker completely and only the memory cache and HTTP cache apply. This is why a PWA's first load must be fast on its own merits. The service worker only improves the second and subsequent loads, and it can only serve offline what it cached during an earlier online visit. The mechanics of that first install are in Lifecycle and Registration & Scope.
Where double caching happens¶
Because the service worker's network requests traverse the HTTP cache, the same bytes can exist in both the HTTP cache and Cache Storage. This is usually harmless and sometimes useful: HTTP revalidation can make a network-first strategy cheap with 304 Not Modified. It becomes a problem in three situations:
- Precaching unversioned URLs. The precache copies whatever the HTTP cache hands back, which may be stale.
- Quota pressure. The HTTP cache does not count against your origin's storage quota, but Cache Storage does. Caching huge media both ways wastes device storage.
- Debugging. Chromium DevTools may show "(disk cache)" for the request made by the worker and "(ServiceWorker)" for the page's view of the same request. Both are true.
Choosing a storage layer by data type¶
Use this table as the default mapping. The right answer for your app may differ, but deviations should be deliberate.
| Data | Recommended layer | Why | Watch out for |
|---|---|---|---|
| App shell HTML, CSS, JS with hashed file names | Cache Storage (precache) + HTTP cache immutable | Served directly to respondWith(); versioned by build | Clean old caches in activate |
| Unhashed HTML documents | Cache Storage (network-first or stale-while-revalidate) | Needs an offline copy but also freshness | redirected responses for navigations |
| Offline fallback page and its assets | Cache Storage (precache) | Must exist before the first offline navigation | Precache it at install, not lazily |
| Web fonts, icons, images | Cache Storage (cache-first with expiration) | Large, rarely change | Opaque cross-origin responses inflate quota |
| API JSON responses you render directly | Cache Storage or IndexedDB | Cache Storage if you replay the response; IndexedDB if you query or merge it | Cached JSON has no expiry of its own |
| Normalized application records (messages, tasks, products) | IndexedDB | Indexes, transactions, partial updates | Schema migrations via onupgradeneeded |
| Pending user mutations (outbox) | IndexedDB | Survives restarts and is readable by Background Sync | Idempotency keys on replay |
| Drafts and form state | IndexedDB (debounced writes) | Crash-safe, worker-readable | Write on pagehide, not unload |
| User files, audio, video to play offline | OPFS or Cache Storage | OPFS for random access and editing; Cache Storage if served by URL | Range requests need 206 responses built by hand |
| SQLite or other embedded database files | OPFS (sync access handle in a worker) | In-place writes, high throughput | Dedicated-worker-only sync API |
| Metadata about cached responses (timestamps, sizes) | IndexedDB | Cache Storage cannot store it | Keep both in sync when deleting |
| Theme, locale, dismissed banners | localStorage | Synchronous read before first paint | Window-only; 5 MiB; strings only |
| Per-tab ephemeral UI state | sessionStorage or memory | Scoped to the tab | Lost on tab close |
| Session identifiers, auth for server rendering | Cookies (HttpOnly, Secure, SameSite) | Sent automatically to the server | Not visible to Cache Storage matching |
| Encryption keys | IndexedDB holding non-extractable CryptoKey objects | Structured clone preserves CryptoKey without exposing key material | Evicted with the rest of the origin |
The same logic as a decision tree:
flowchart TD
A[New piece of data] --> B{"Will you serve it as the response to a request?"}
B -- Yes --> C{"Is plain HTTP caching enough, with no offline guarantee?"}
C -- Yes --> D[HTTP cache via Cache-Control]
C -- No --> E[Cache Storage via the service worker]
B -- No --> F{"Structured records you query or update?"}
F -- Yes --> G[IndexedDB]
F -- No --> H{"Large file or random-access binary data?"}
H -- Yes --> I[OPFS]
H -- No --> J{"Tiny setting read synchronously on the main thread?"}
J -- Yes --> K[localStorage]
J -- No --> G Shared quota, eviction and persistence¶
Cache Storage, IndexedDB, OPFS and service worker registrations all live in the same storage bucket for your storage key, as defined by the WHATWG Storage Standard. They share one quota, and they are evicted together. The HTTP cache and cookies are separate and do not count. The headline numbers as of September 2026, all documented by MDN or WebKit:
| Engine | Per-origin quota | Eviction |
|---|---|---|
| Chromium (Chrome, Edge) | Up to 60% of total disk | LRU by origin under storage pressure; persistent origins exempt |
| Firefox | Best-effort: 10% of disk or 10 GiB group limit, whichever is smaller. Persistent: 50% of disk, up to 8 TiB | LRU by origin; persistent origins exempt |
| Safari 17+ (macOS 14, iOS 17 and later) | Browser apps and Home Screen web apps: about 60% of disk. WKWebView apps: about 15% | LRU by origin; plus deletion of script-writable storage after 7 days of Safari use without interaction (not for Home Screen apps) |
Four consequences for offline design:
- Eviction is all-or-nothing per origin. Browsers delete every store together so that an app never sees IndexedDB records pointing at Cache Storage entries that no longer exist. Your app must still handle waking up with nothing cached, because that is what eviction looks like.
- Ask for persistence when it matters.
navigator.storage.persist()exempts the bucket from pressure-based eviction when granted. Chromium and Safari decide without a prompt based on engagement signals such as installation. Firefox shows a permission prompt. - Safari's seven-day cap applies to browser tabs, not installed apps. WebKit deletes all script-writable storage, including service worker registrations and Cache Storage, after seven days of Safari use without user interaction on the site. Home Screen web apps keep their own usage counter and are not expected to lose first-party data.
- Third-party iframes get partitioned storage. Chrome partitions Cache Storage, IndexedDB and service workers by top-level site in third-party contexts since Chrome 115, and the other engines partition as well. An embedded widget cannot share an offline cache with its first-party site.
navigator.storage.estimate() returns an approximate usage and quota (Safari since 17). Chromium adds a non-standard usageDetails breakdown. Chromium also ships the Storage Buckets API (Chrome 122), which lets you create separately evictable buckets with their own persistence and expiry. Chrome's documentation states that its implementation covers IndexedDB only, although MDN's compatibility data lists a caches attribute on buckets, so feature-detect before using a bucket for Cache Storage. Full detail, including private-browsing limits and quota error handling, is on Storage Quotas & Persistence. The privacy side of partitioning is on Privacy & Storage Partitioning.
Clearing storage from the server
The Clear-Site-Data response header's "cache" directive targets the HTTP cache and other browser-internal caches, which depending on the browser include prerendered pages, bfcache entries and script caches. Cache Storage is not a browser cache in that sense: it is origin storage that lives in the storage bucket, and it is cleared together with IndexedDB, Web Storage and service worker registrations by the "storage" directive. Send Clear-Site-Data: "cache", "storage" (or "*") from a logout endpoint when cached responses may contain personal data, and delete per-user caches from script as well so that the cleanup does not depend on the header alone.
Offline support is a spectrum¶
"Works offline" can mean very different amounts of engineering. It helps to pick a level deliberately, because each level implies a different mix of the layers above:
- Custom offline page. Precache one fallback HTML page and its assets, and return it when a navigation's
fetch()rejects. This needs Cache Storage and a dozen lines of service worker code. It is the minimum that stops the browser's offline error page from appearing inside an installed app. - Cached shell, network content. Precache the app shell and runtime-cache recently viewed pages or API responses. Users can reopen what they already saw. Cache Storage with runtime strategies and expiration covers this level.
- Offline reading of chosen content. Let users explicitly save articles, maps or media. Window code writes those entries into a dedicated cache or into OPFS, and a quota-aware UI shows how much space they use.
- Offline-first read and write. The UI reads from IndexedDB, and writes go to a local outbox that syncs when connectivity returns. The network is an optimization. This level needs IndexedDB, conflict resolution, and usually Background Sync. See Offline-First Data & Sync.
Whatever the level, the user interface has to communicate state: what is available offline, what is stale, and what is still pending upload. That is covered in Offline UX & Fallbacks.
How this section is organized¶
The pages build on each other in roughly this order. Read Cache Storage API first, then Caching Strategies and Precaching & Runtime Caching. Read the storage pages when your app needs structured data, files or guarantees about persistence.
-
Cache Storage API
Every method of
CacheStorageandCache, the matching algorithm,addAll()atomicity, opaque responses, expiration, LRU trimming and size measurement. -
Caching Strategies
Cache-first, network-first, stale-while-revalidate, cache-only and network-only, with timeouts, fallbacks and per-route selection.
-
Precaching & Runtime Caching
Build-time manifests, revisioning, install-time downloads, cache cleanup on activate, and runtime caches with limits.
-
Offline UX & Fallbacks
Offline pages, placeholder images, connectivity detection, stale-content indicators and queued-action feedback.
-
HTTP Caching & Service Workers
How
Cache-Control, validators andVaryinteract with a service worker,fetch()cache modes, and header recipes for PWAs. -
Storage Quotas & Persistence
Per-browser quotas,
estimate(),persist(), eviction policies, private browsing and handlingQuotaExceededError. -
IndexedDB
Transactions, indexes, cursors, versioned schema migrations, durability hints and using IndexedDB from service workers.
-
Origin Private File System
getDirectory(), sync access handles in workers, streaming writes and running SQLite on OPFS.
Browser support¶
Support data as of September 2026. For live data, check MDN's Cache interface compatibility table and caniuse.
| Feature | Chrome / Edge | Firefox | Safari |
|---|---|---|---|
| Cache Storage in windows and workers | ✅ 43 | ✅ 41 (workers 44) | ✅ 11.1 (iOS 11.3) |
| IndexedDB (including in service workers) | ✅ | ✅ | ✅ |
OPFS getDirectory() | ✅ 86 | ✅ 111 | ✅ 15.2 |
OPFS createSyncAccessHandle() (dedicated workers) | ✅ 102 | ✅ 111 | ✅ 15.2 |
navigator.storage.estimate() | ✅ 61 | ✅ 57 | ✅ 17 |
estimate().usageDetails | ✅ 61 | ❌ | ❌ |
navigator.storage.persist() | ✅ 55 | ✅ 57 | ✅ 15.2 |
| Storage Buckets API | ✅ 122 ⚠️ | ❌ | ❌ |
Static routing with a cache source | ✅ 123 | ❌ | ✅ 27 |
| Back/forward cache | ✅ | ✅ | ✅ |
⚠️ Chrome's Storage Buckets article describes the implementation as IndexedDB-only, while MDN's compatibility data lists StorageBucket.caches and getDirectory() from Chrome 122. Feature-detect bucket.caches before you rely on it for Cache Storage.
Safari versions are for macOS. Safari on iOS and iPadOS shipped Cache Storage with service workers in iOS 11.3. The other Safari rows apply to iOS at the same version number.
Common pitfalls¶
- Treating Cache Storage like the HTTP cache. Entries never expire on their own. Without explicit cleanup and expiration, a runtime cache grows until the origin hits its quota and gets evicted wholesale.
- Putting the offline source of truth in
localStorage. The service worker cannot read it, and background sync cannot replay it. - Precaching through a stale HTTP cache. Use hashed URLs or request precache entries with
cache: "reload". - Caching opaque cross-origin responses casually. Each one can cost megabytes of quota in Chromium, and you cannot tell a 200 from a 500. Request them with CORS when the server allows it.
- Forgetting that storage is per origin, not per app. Two PWAs or two service worker scopes on the same origin share Cache Storage and IndexedDB. Prefix cache names and delete only your own.
- Assuming data is still there. Eviction, Safari's seven-day cap for non-installed sites, the user clearing site data, and private windows all produce an empty store. Every read path needs a network or empty-state fallback.
- Caching personalized responses without cleanup. Cache Storage matching ignores cookies and credentials. Delete user-specific caches on logout.
Debugging¶
In Chromium DevTools, the Application panel lists Cache Storage (with each entry's headers, a preview and "Time Cached"), IndexedDB, Local and Session Storage, and a Storage view that shows usage per type and a Clear site data button. Its Back/forward cache view tests bfcache eligibility and lists blocking reasons. Firefox's Storage Inspector exposes Cache Storage, IndexedDB and Web Storage. From any console you can dump Cache Storage directly:
for (const name of await caches.keys()) {
const cache = await caches.open(name);
const requests = await cache.keys();
console.groupCollapsed(`${name}: ${requests.length} entries`);
requests.forEach((request) => console.log(request.method, request.url));
console.groupEnd();
}
console.table(await navigator.storage.estimate());
For systematic workflows, including simulating offline, bypassing the service worker and inspecting quota, see Browser DevTools.
Further reading¶
On this site
- Cache Storage API: the full API reference and utility code
- Caching Strategies: choosing a strategy per route
- Handling Fetch Events:
respondWith(),waitUntil()and request routing - Static Routing API: serving from cache without starting the worker
- Offline-First Data & Sync: IndexedDB-backed data models and conflict handling
- App Shell Model: what to precache for instant loads
- Workbox Fundamentals: a library implementation of these patterns
External references
- Service Workers specification, Caches section (W3C)
- Storage Standard (WHATWG): storage keys, buckets and persistence
- RFC 9111: HTTP Caching
- Storage quotas and eviction criteria (MDN)
- Updates to Storage Policy (WebKit, Safari 17 quota changes)
- Full Third-Party Cookie Blocking and More (WebKit, the seven-day storage cap)
- Back/forward cache (web.dev) and bfcache for Cache-Control: no-store (Chrome)
- Gaining security and privacy by partitioning the cache (Chrome)
- A Tale of Four Caches (Yoav Weiss)