Progressive Web Apps

A Progressive Web App (PWA) is a web app with native-app-like features: offline support, push notifications, installable on home screen, background sync. Built using web standards: service workers, manifests, web APIs.

The PWA promise was that one codebase replaces native apps. The reality is more nuanced.

The core technologies

Service workers

A JavaScript worker that runs separately from your page. It can intercept network requests, cache responses, work offline.

// Register
navigator.serviceWorker.register('/sw.js');

// In sw.js
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then(response => response || fetch(event.request))
    );
});

Service workers enable:

Web App Manifest

A JSON file describing the app:

{
    "name": "My App",
    "short_name": "MyApp",
    "start_url": "/",
    "display": "standalone",
    "icons": [...]
}

The browser uses this for "install to home screen" — the app gets its own icon and runs in a standalone window.

Cache API

Programmatic control over what's cached. Used inside service workers.

Push API

For server-to-client push notifications via a push service (Firebase Cloud Messaging, etc.).

What PWAs do well

Offline support

Cached resources serve when network is down. For content-reading use cases (news, docs), this is significant.

Reliable performance

Cached assets load instantly. Less network dependency.

Installable

Users can add to home screen; the app feels more committed than a website.

Rich web APIs

Background sync, push notifications, file system access, camera, geolocation — increasingly available via web APIs.

What PWAs don't do (or do imperfectly)

Match native performance

Heavy graphics (games, video editing), tight integration with OS features. Native is still ahead for these.

iOS feature parity

Apple has historically been slow to adopt PWA features. Push notifications on iOS Safari only arrived recently. Some features still missing.

App store distribution

Most users find apps via App Store / Play Store. PWAs aren't there by default (though some stores accept them).

Deep OS integration

Native app developer experience for things like share extensions, widgets, watch apps.

The honest assessment

PWAs work well for:

PWAs work less well for:

For many use cases, "make the website work great offline" is sufficient. For others, native or React Native is the answer.

The Service Worker patterns

Cache-first

Try cache; fall back to network.

caches.match(request)
    .then(cached => cached || fetch(request))

For static assets that rarely change.

Network-first

Try network; fall back to cache.

fetch(request).catch(() => caches.match(request))

For data that should be fresh; cache as fallback.

Stale-while-revalidate

Serve cached; update cache in background.

caches.match(request).then(cached => {
    fetch(request).then(fresh => cache.put(request, fresh));
    return cached || fetch(request);
});

Best of both for most resources.

Cache only

Serve from cache; never go to network. For truly static content.

Network only

Always go to network. For requests that must be fresh.

Implementation considerations

Service worker debugging

Service workers can be tricky to debug. They cache aggressively; old service workers persist. DevTools have specific service worker panels.

Update strategies

When a new service worker is published, the old one is still running for existing tabs. Until tabs close, users see old behavior. Strategies:

Storage limits

Browsers limit cache storage (varies by browser; tens to hundreds of MB). Plan caching strategy accordingly.

HTTPS required

Service workers require HTTPS (or localhost). Production PWAs need TLS.

Tools

Workbox

Google library for service worker patterns. Reduces boilerplate.

PWA frameworks

Next.js, Nuxt, SvelteKit — all have PWA plugins or built-in support.

Lighthouse

Browser tool; audits PWA conformance. Useful for checking compliance.

Common failure patterns

Further Reading