Definition
When you add an offline "shell fallback" to a Service Worker (a background script that intercepts requests between the page and the network), it is easy to write it so that when the network fails it returns the app-shell HTML (e.g., /) instead. But if you apply this fallback to every request, then when image/API requests fail it also returns HTML, so <img> breaks and blobs pile up endlessly in the shell cache. The shell fallback must be applied only to document-navigation requests where request.mode === 'navigate'.
The shell (app shell) is the minimal HTML holding the skeleton of the screen; request.mode is the marker that distinguishes whether the request is a page navigation (navigate) or a subresource like an image or script.
Why it matters
Symptom: photos appear broken, and over time browser storage swells with unidentifiable data. The cause is that the SW is written to intercept every same-origin GET and, on failure, fall back to the shell ("/"). When a photo request takes the shell fallback due to a network failure, <img> receives an HTML document instead of an image and fails to render. On top of that, that HTML accumulates in the cache keyed by the photo URL, polluting storage.
If only document navigations (page loads) fall back to the shell, then images, APIs, and static assets receive the network result (or their own cache strategy) as-is, and this pollution disappears.
How it works
The crux is that "the shell is only a fallback for document requests." Images and APIs each get their own policy (e.g., a stale-while-revalidate cache for images, straight-to-network for the API).
Applied practice
- Branch the SW cache strategy by request type:
navigate→ shell fallback, image GET → SWR cache, API → network (+offline queue). - If polluted cache already exists, bump the cache version (
v1→v2) to purge past entries. - Before adding a shell fallback, always check "does this fallback also apply to non-document requests?"
Trade-offs
- Navigation-only shell fallback: even offline, the page skeleton shows, and images/APIs are handled as clean failures (blank image/error) instead of broken HTML. In exchange, you must write the per-request-type branching yourself.
- Full shell fallback (anti-pattern): the code is shorter, but it invites broken images + cache pollution.
When not to use it
- An app that does not use a SW at all — first check whether a server CDN / HTTP cache is enough.
- Cases where displaying an offline document is not needed — the shell fallback itself is unnecessary.
Common mistakes
- Applying the shell fallback to every GET, returning HTML on image/API failures.
- The fallback HTML accumulating in the cache keyed by the resource URL, polluting storage.
- Not bumping the cache version after fixing cache pollution, so past pollution remains.
Related concepts
- service-worker-stale-while-revalidate — the cache strategy for subresources such as images
- server-image-proxy-transcoding-cache — dividing roles between proxy image URLs and the cache