Your PWA opens to a blank screen

Short answer

A PWA that opens to a blank screen is nearly always a service worker that answers the request but has nothing to answer with. The four causes, in the order we actually encounter them: the worker is registered but its cache is empty; the app shell is cached but the data it needs is not; the cache holds files from a previous deploy that no longer exist; or the manifest points somewhere that does not load. Open devtools → Application → Service Workers, tick Offline, and reload — the cause is visible in under a minute.

A registered worker with an empty cache

This is the one we see most often, and it is the most misleading, because every surface-level check says the app is fine. The service worker is registered. It is activated. Chrome offers to install the app. And then it opens white, because the worker intercepts the request and finds nothing stored to serve.

We measured 386 installable web apps for this directory. 235 of them install perfectly and then show nothing once the network drops — 61%. Almost none of their developers know it, because the failure only appears in a state they never test: installed, launched, no connection.

The check takes thirty seconds. Devtools → Application → Cache Storage. If there are no caches, or a cache with zero entries, this is your cause. A worker that calls caches.open() but never addAll() produces exactly this.

The shell is cached, the data is not

The app frame loads — header, navigation, maybe a spinner — and the content never arrives. This happens when the worker deliberately excludes the API from caching, which is a reasonable default and a trap in a specific case.

We ran into a clean example this week: a self-hosted grocery-list app whose service worker skipped /api/* on purpose, so the shell cached and the lists did not. The author built it to tick items off in a supermarket aisle — precisely the place with the worst signal, and precisely the case that broke. The app installed and opened there. It just could not tell them what to buy.

The fix is not to cache the API blindly. Cache GET responses stale-while-revalidate so the last known data is there, and queue writes in IndexedDB for replay. Be aware that this makes sync genuinely harder the moment two people can edit offline — a real cost worth weighing.

The cache holds a previous deploy

A returning user gets the blank screen, a first-time visitor does not. That asymmetry is the signature: the worker is serving an index.html that references main.a1b2c3.js, and that file no longer exists on your server because the last build renamed it.

Cache-first strategies cause this whenever the cache name does not change between releases. The cure is a single version constant bumped on every deploy — one knob, changed once, invalidating everything — plus precaching with cache: 'reload' so the browser's own HTTP cache cannot hand you a stale copy of the new file.

If you want to confirm before changing anything: devtools → Application → Cache Storage, open the cache, and compare the filenames against what your server actually has.

The manifest points somewhere that does not load

Less common than the others, and easy to miss because the app works fine in a browser tab. The installed version launches start_url, and if that path 404s, redirects to a login the app cannot render offline, or falls outside scope, you get a white window.

This category is larger than it looks. Scanning the manifests of every app in this directory on 12 August 2026, 3 of them declared a manifest that did not respond at all. Those apps cannot be installed by anyone, and nothing in a normal browsing session reveals it.

Check start_url and scope in your manifest, then open start_url directly in a private window. If it does not render on its own, it will not render in the installed app either.

The one-minute diagnosis

Open devtools, go to Application → Service Workers, tick **Offline**, and reload. Then read three things in order: is a worker activated, does Cache Storage contain entries, and does the Network tab show requests failing rather than being served from the worker.

That sequence separates all four causes. No worker means nothing was ever going to work offline. A worker with an empty cache is cause one. A worker serving the shell while API calls fail is cause two. A worker serving files your server no longer has is cause three. And a worker that is fine while start_url itself fails is cause four.

If you would rather have someone else run it, we do this measurement on request — we open the app in a real browser, cut the network, and send back what survived.

Frequently asked

Why does it work in the browser but not once installed?

Because the installed app launches start_url in its own window with no browser chrome and, often, no network. A tab reuses whatever is already loaded and has your full session; the installed shortcut starts cold. Anything that depends on a live request, a redirect, or a page you happened to already have open will fail there and nowhere else.

Is a blank screen always the service worker's fault?

Not always, but it is where to look first, because the worker is the only component that can answer a request with nothing. Rule it out before investigating your framework: tick Offline in devtools and check Cache Storage. If the cache is populated and the shell still does not paint, then it is your app code.

How common is this really?

Of 386 installable web apps we measured for this directory, 235 install and then show nothing with the network off. That is 61%. It is not a rare edge case — it is the normal state of the ecosystem, mostly because the failing state is one nobody tests.

Also published on Medium. This page is the original.

Apps mentioned

Keep reading

One email a month. Only the apps worth your time.

What they do, what they don't, and whether they actually work offline — we check. No roundup of everything we listed, no affiliate links.

Double opt-in — nothing is sent until you confirm. Unsubscribe any time.
Looking for more apps that work offline?

592+ Progressive Web Apps, each checked by hand — installable from your browser, no app store.

Browse the directory →