DevelopmentAll levels

PWA

A PWA — a progressive web app — is not a framework and not a format, but a set of browser capabilities that let a website install to the home screen, work offline and behave like an app. There is no PWA SDK: you add a service worker, a manifest and some HTTPS, and what you get depends on which browser the user has. This page covers the service worker, installability, what iOS actually permits, and how to decide between a PWA and a native app honestly.

What it is
Browser capabilities
Requires
HTTPS, worker, manifest
Distribution
A URL, no review

The service worker

Everything a PWA can do that an ordinary site cannot comes from one mechanism. A service worker is a script the browser runs outside the page, and once it is registered, every network request the page makes passes through it first. For each one the worker decides: answer from a cache, go to the network, or do both and choose. The page has no idea this is happening and no way around it.

A service worker is a script the browser keeps outside the page, and every request the page makes passes through it. Per request, it decides whether to answer from a cache or go to the network, which is what makes offline use, instant repeat loads and background sync possible at all. The same design creates the update problem: a new worker installs but then waits until every tab using the old one is closed, so a shipped fix does not reach users on the next load unless the page handles the update deliberately.

Which strategy to use is a per-request decision, not a global setting, and the choice is about how stale an answer is allowed to be. Serve the application shell — the HTML, CSS and JavaScript — cache-first, because those are versioned and a cached copy is correct until you deploy. Serve API data network-first with a cached fallback, so a user with signal sees current data and a user in a tunnel sees the last thing they had. Use stale-while-revalidate where instant is worth more than fresh: show the cache immediately, fetch in the background, update for next time.

Write the worker with a library rather than by hand. Workbox generates the precache manifest from your build output, so the file list and its version hashes stay correct as the bundle changes, and it implements the strategies above as a few lines each. A hand-written worker is a plausible weekend project and an unwise thing to have in production, because the failure mode is serving stale content to real users with no way to intervene.

What makes it installable

The second half of a PWA is the web app manifest: a small JSON file naming the app, its icons, its theme colour, its start URL and its display mode. Set display to standalone and the installed app opens without browser chrome — no address bar, no tabs — which is most of what makes it feel like an app rather than a bookmark. Serve it over HTTPS, which is required for service workers anyway.

What installation actually gets you is worth being precise about, because it is more than a shortcut and less than an app store listing. The app gets its own icon and its own window, appears in the task switcher as a separate entity, and keeps its own storage. It does not get a listing anybody can find by browsing, it does not get the store's payment system, and on most platforms the user has to be told the option exists — the browser will not always offer it prominently.

The limits, stated honestly

PWA capability is not one thing — it is a list of APIs, each supported by some browsers and not others, and the gap is not evenly distributed. Chromium browsers on Android implement almost all of it. Safari on iOS implements the core and withholds several things teams tend to assume, and since a PWA on iOS runs in Safari's engine regardless of which browser installed it, there is no way to route around it.

CapabilityAndroid / ChromiumiOS / Safari
Offline and cachingFull supportSupported; storage can be evicted when unused
Install to home screenPrompted by the browserManual, through the share menu — users need telling
Push notificationsYesOnly when installed to the home screen
Background syncYesNo
Bluetooth, NFC, USBYes, behind permissionNo

The decision this leads to is simpler than the comparison suggests. If the product is content, commerce, booking, dashboards or internal tooling — things that are fundamentally a website that would benefit from working offline and living on the home screen — a PWA is the cheaper answer and reaches everyone through a URL. If the product depends on hardware access, on background behaviour while it is closed, on store discovery, or on being present in a market where users find apps by browsing a store, then it needs to be an app. Everything in between is a judgement about how much the missing capability costs you.

How this shows up in real delivery

The advantage that survives every comparison is distribution. There is no review queue, no store account, no certificate to renew and no build to sign — a fix is a deploy, and a link is the whole installation flow. For products that live on being found through search or shared as a URL, that is not a minor operational convenience: it is the difference between a user trying the product now and a user being asked to install something first.

Two things need designing rather than assuming. Storage is not permanent — a browser may evict a site's data under pressure or after long disuse, so anything the user would be upset to lose has to reach a server, and navigator.storage.persist() is a request rather than a guarantee. And offline is a state the interface has to have, not a technical detail: show what is cached, mark what is stale, queue what was written while disconnected, and tell the user which of those they are looking at. An app that silently shows week-old data as if it were current is worse than one that says it is offline.

Where it degrades

  • No update handling, so a shipped fix waits behind an old worker for as long as a tab stays open.
  • A hand-written service worker whose cache list drifts out of step with the build output.
  • The service worker file itself cached, which can strand users on a version you cannot replace.
  • Cache-first applied to API data, so the app confidently shows information that is days old.
  • Offline treated as an error state rather than a designed one, with a blank screen as the result.
  • Data kept only in browser storage, which the browser is entitled to evict.
  • A feature planned on an API that iOS does not implement, discovered after the design was approved.

When to use it

Use it when

  • Products that are fundamentally a website and would benefit from offline use and a home-screen icon.
  • Anything discovered through search or shared as a link, where an install step costs you users.
  • Internal tools and field applications, where you control the devices and the review queue is pure overhead.
  • Teams that need one codebase across desktop and mobile and cannot staff two native platforms.

Avoid it when

  • Apps that need hardware access — Bluetooth, NFC, USB — on iOS, where those APIs do not exist.
  • Anything depending on work happening while the app is closed, which the web platform does not guarantee.
  • Products whose users find apps by browsing a store, where a URL reaches nobody.
  • Graphics-heavy or latency-critical experiences, where the browser is the wrong runtime.

Found this useful?

Share it with someone who is working on the same problem.