PWA Offline Support: How It Works and What It Can and Can't Do (2026)
PWA Offline Support: How It Works and What It Can and Can't Do (2026)
"Works offline" is one of the headline promises of progressive web apps, and it is also the promise most often misunderstood. PWA offline support does not mean your whole app keeps running without a network. It means you control, in JavaScript, what happens when a request cannot reach the server — and that control has clear limits.
This guide explains the mechanism behind PWA offline support, the four caching patterns worth knowing, the things offline mode genuinely cannot do, and how to test it before you ship.
What PWA Offline Support Actually Means
A normal website has no answer when the network fails. The browser owns that moment and shows its own error page.
A progressive web app installs a service worker — a background script that sits between your pages and the network. Every outgoing request passes through it first. When the network is unavailable, the service worker can answer from a local cache instead of failing.
So "offline support" is more precisely: a request-interception layer plus a storage layer that you program yourself. Nothing is cached automatically because you installed a service worker. If you do not write caching rules, an installed PWA fails offline exactly like a plain website.
Two storage systems do the work:
- Cache Storage — stores whole HTTP responses (HTML, CSS, JS, images, fonts). This is what the service worker reads from when replaying a request.
- IndexedDB — stores structured data (records, drafts, queued actions). This is where you keep the things a user typed while offline.
How Service Workers Make Offline Support Work
The service worker lifecycle has three stages, and each one matters for offline behaviour.
Install. Fires once when the browser picks up a new service worker file. This is where you pre-cache the "app shell" — the minimum set of files needed to render something useful: the shell HTML, core CSS, the main JS bundle, the logo, and an offline fallback page.
Activate. Fires when the new worker takes control. This is where you delete caches from previous versions. Skipping cleanup is the most common cause of a PWA that serves stale assets for weeks.
Fetch. Fires on every request the page makes. Your handler decides: go to the network, read from cache, or combine the two. This is where offline support actually happens.
A minimal shape looks like this:
const CACHE = 'shell-v3';
const SHELL = ['/', '/offline.html', '/app.css', '/app.js'];
self.addEventListener('install', e => {
e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL)));
});
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
)
);
});
self.addEventListener('fetch', e => {
if (e.request.mode === 'navigate') {
e.respondWith(fetch(e.request).catch(() => caches.match('/offline.html')));
}
});Three constraints are worth internalising early. The service worker file must be served over HTTPS (localhost is exempt for development). Its scope is limited to its own directory and below, so a worker at /js/sw.js cannot control /. And it runs in a separate thread with no access to the DOM — it can only talk to pages through messages.
Four Caching Strategies and When Each One Fits
Almost every real offline setup is a mix of four patterns, chosen per request type rather than applied globally.
Cache first. Check the cache; only touch the network on a miss. Right for fingerprinted static assets — versioned CSS and JS bundles, fonts, icons. These never change under a given URL, so serving them from disk is both faster and safe.
Network first. Try the network; fall back to the cache when it fails. Right for content that goes stale — account pages, order status, anything with a number in it that users expect to be current. Online users always get fresh data, and offline users get the last known version instead of an error.
Stale while revalidate. Serve the cached copy immediately, fetch a fresh copy in the background, update the cache for next time. Right for content that should feel instant but does not need to be perfectly current — article bodies, avatars, product listings. The user sees one-version-old content, which is usually the correct trade.
Network only. Never cache. Right for anything mutating or sensitive — payments, authentication, one-time tokens. Some requests should simply fail when offline rather than succeed against stale state.
A practical default for most sites: cache first for /assets/*, network first for HTML navigations, stale-while-revalidate for images, network only for /api/checkout and auth.
What PWA Offline Support Cannot Do
This is the part that surprises teams mid-project.
It cannot serve content that was never cached. Offline support replays what the device already has. A page a user has never visited, and that you never pre-cached, is unavailable offline. There is no magic mirror of your site.
It cannot complete server-side work. Payments, inventory checks, sign-ins, anything requiring a server response cannot finish offline. The honest pattern is to queue the intent locally in IndexedDB and replay it when connectivity returns — the Background Sync API does exactly this, though support is not universal, so keep a manual retry path.
Storage is not guaranteed to persist. Browsers evict cached data under storage pressure, and eviction is not something you get to veto. navigator.storage.persist() requests durable storage, and the browser may decline. Never treat Cache Storage or IndexedDB as the only copy of user data.
iOS behaves differently. Service workers work in Safari, but storage limits are tighter and data for rarely used sites can be cleared after roughly seven days of inactivity. An offline experience validated only on Android will not reflect what iOS users see.
The first visit is never offline. A service worker must be downloaded, installed and activated before it can do anything — which requires a network. Offline support protects the second visit onward.
Testing Offline Support Before You Ship
Offline bugs do not surface in ordinary QA, because ordinary QA has Wi-Fi. Test it deliberately:
- DevTools → Application → Service Workers → Offline. Toggle it and reload. This simulates a dead network while keeping the page open.
- Check Cache Storage contents directly. DevTools → Application → Cache Storage lists exactly what is stored. If a file is not there, it will not be available offline, regardless of what your install handler intended.
- Test the update path, not just the install path. Ship a change, reload twice, and confirm the new version takes over and old caches are deleted. Serving version 1 to a user who reloaded after version 3 shipped is the classic failure.
- Turn the device radio off, not just DevTools. Real airplane-mode behaviour differs from simulated offline, especially on iOS.
- Test a cold start while offline. Close all tabs, go offline, open the app from the home screen icon. This is the scenario users actually hit and the one most likely to reveal a missing shell asset.
A useful acceptance bar: with the radio off and no tabs open, launching from the home screen should show your interface and a clear "you're offline" state — never the browser's error page.
Offline Support in a Distribution Context
Offline capability is often the reason teams look at PWAs in the first place — particularly teams distributing outside app stores, where a web app has to feel close enough to an installed app to be credible. In that context offline support is one component among several: install prompts, push notifications, and how the app behaves on a poor connection all shape whether the experience reads as an app or as a website.
If you are weighing that trade-off more broadly, PWA vs APK covers how the two distribution routes differ in practice, and ROIBest works on the delivery side of Android PWA distribution.
Frequently Asked Questions
Does offline support require a manifest file? No. Offline behaviour comes entirely from the service worker. The web app manifest controls installability — name, icons, display mode. They are separate features that are usually shipped together.
How much can I cache? It varies by browser and available disk. Chrome typically allows a large share of free disk space per origin; Safari is considerably tighter. Design for tens of megabytes, not gigabytes, and cache selectively.
Will caching break my analytics? It can. Cached page views may not reach your server. Send analytics events from the page rather than inferring them from server logs, and queue events while offline if the numbers matter.
Can I make an existing site work offline without rewriting it? Partly. Adding a service worker with a network-first strategy plus an offline fallback page is a contained change and delivers most of the perceived benefit. Full offline functionality — creating and editing data without a connection — usually does require architectural work.


