Progressive Web Apps represent a fundamental shift in how we build and distribute applications. This deep dive explores the architecture, trade offs, and real world patterns that make PWAs a compelling alternative to the App Store model.
██████╗ ██╗ ██╗ █████╗ ██╔══██╗██║ ██║██╔══██╗ ██████╔╝██║ █╗ ██║███████║ ██╔═══╝ ██║███╗██║██╔══██║ ██║ ╚███╔███╔╝██║ ██║ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝
A Progressive Web App is a web application built with modern browser APIs that delivers app like experiences. The key technologies are service workers (for offline caching and push notifications), a web app manifest (which tells the browser how the app should look and behave when installed), and HTTPS (mandatory for service worker registration).
Unlike native apps that run directly on the operating system, PWAs run inside a browser engine. When you "install" a PWA to your home screen, you are creating a shortcut that opens the web app in a standalone browser window without the URL bar.
PWAs are not a new technology stack. They are a collection of browser APIs (Service Worker, Cache API, Web App Manifest, Push API) combined into a coherent pattern. The innovation is the pattern, not the individual pieces. Each API existed before the PWA term was coined in 2015.
The manifest is a JSON file that tells the browser how to display your app when installed. It defines the app name, icons, theme colors, display mode, and start URL. Without a valid manifest, browsers will not show the install prompt.
// manifest.json
{
"name": "Architecture DeepDive",
"short_name": "DeepDive",
"description": "Technical deep dives into system architecture",
"start_url": "/",
"display": "standalone",
"background_color": "#0a0e27",
"theme_color": "#00d9ff",
"orientation": "portrait",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Service workers can intercept every network request your app makes. They act as a programmable proxy between your app and the network. This power creates significant security implications. A compromised service worker could inject malicious content into every page. HTTPS is mandatory to prevent man in the middle attacks during service worker registration.
The app shell is the minimal HTML, CSS, and JavaScript required to render your app's UI skeleton. It loads instantly from the cache while dynamic content fetches from the network. This pattern creates the perception of instant loading even on slow connections.
A service worker is a JavaScript file that runs in the browser background, separate from your web page. Every request your page makes passes through the service worker, which can intercept it, serve a cached response, fetch from the network, or do both. Service workers also handle push notifications and background sync.
Service workers run on their own thread. They have no DOM access. They communicate with pages via the postMessage API. They can run even when no page is open (for push notification handling).
Register your service worker as early as possible in the page lifecycle. Wrap registration in a feature check so the page gracefully degrades in browsers that do not support service workers.
// Register service worker on page load
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registered:', registration.scope);
} catch (error) {
console.error('SW registration failed:', error);
}
});
}
The folder your service worker sits in determines its scope. A service worker at /app/sw.js can only control requests under /app/. Place your service worker at the root to control all requests. Only one service worker is allowed per scope.
A new service worker is installed in the background, but the old one keeps controlling pages until all tabs are closed. Refreshing a page does not activate the new worker. Users must close all tabs using the old worker and navigate back. This catches many developers off guard during deployment.
The power of service workers lies in how you handle fetch events. Each request can be routed through different strategies depending on your requirements for freshness, speed, and offline capability.
// Stale While Revalidate Strategy
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic-v1').then(async (cache) => {
const cachedResponse = await cache.match(event.request);
// Fetch fresh version in background
const fetchPromise = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
// Return cached immediately, or wait for network
return cachedResponse || fetchPromise;
})
);
});
Workbox is Google's library for service worker development. It provides pre built caching strategies, precaching support, and routing utilities. In 2026, Workbox remains the standard toolkit for service worker strategies and integrates cleanly with Vite, webpack, and Next.js (and similar) build pipelines via plugins and injectManifest patterns. The Chrome DevTools Application panel provides complete debugging for service workers, cache storage, and manifest inspection.
Always version your cache names (e.g., static-v2). In the activate event, delete old caches to prevent storage bloat. Safari imposes a 50MB limit on cache storage for PWAs. iOS can automatically clear PWA storage if the app is not used for a few weeks.
| Capability | PWA | Native iOS | Native Android |
|---|---|---|---|
| Development Cost | ✓ Single codebase | ✗ Separate Swift/SwiftUI | ✗ Separate Kotlin |
| App Store Distribution | ✗ Not directly | ✓ Full access | ✓ Full access + PWA |
| Push Notifications | ◐ iOS 16.4+, limited | ✓ Full support | ✓ Full support |
| Offline Capability | ✓ Service workers | ✓ Full control | ✓ Full control |
| Hardware Access | ◐ Camera, GPS, limited | ✓ Full access | ✓ Full access |
| Background Processing | ✗ Very limited | ✓ Background tasks | ✓ Background services |
| Performance | ◐ Good, not native | ✓ Optimal | ✓ Optimal |
| Updates | ✓ Instant, no review | ✗ App Store review | ✗ Play Store review |
| SEO / Discoverability | ✓ Fully indexable | ✗ ASO only | ✗ ASO only |
| Payment Commissions | ✓ 0% (your processor) | ✗ 15 to 30% | ✗ 15 to 30% |
PWAs bypass app stores entirely, offering independence and easier distribution. No 15 to 30% commission. No review delays. No geographic restrictions. Users can access your app instantly via URL. But you lose App Store discoverability and the trust signal of being in a curated marketplace.
Apple has been historically reluctant to fully support PWAs. All iOS browsers must use WebKit (Safari's engine), giving Apple complete control over web capabilities on iOS. This limits competition and innovation in web standards on iOS. PWA features depend entirely on Apple's implementation priorities.
No App Store presence: PWAs cannot be listed in the Apple App Store, cutting them off from the primary discovery channel US users rely on.
Storage limits: Safari historically imposed tight cache quotas (often cited around 50MB for some PWA contexts); treat storage as ephemeral and design for eviction. iOS may clear PWA storage if the app is unused for weeks.
Install UX (2026): There is still no beforeinstallprompt on iOS. Users add via Share → Add to Home Screen. From iOS 16.4+, install is available from the Share menu in Safari and other browsers. Guide users with explicit UI copy—do not rely on Chrome-style install banners.
No background sync: Background processing is heavily limited to save battery.
No install prompt: Users must manually use Share > Add to Home Screen. No automatic install banner.
Push notification friction: Permission must be requested from within the installed PWA. Service worker listeners may not trigger reliably after device restarts.
In April 2025, the EU fined Apple 500 million euros for DMA non compliance related to browser engine restrictions. The UK CMA followed in October 2025 by designating Apple with Strategic Market Status. However, as of early 2026, these rulings have not materially changed PWA capabilities on iOS. Apple's BrowserEngineKit implementation creates enough friction that zero browsers have adopted alternative engines.
Android treats PWAs as first class citizens with broad API support. iOS treats them as a limited subset of what the web can do. If your user base is iOS heavy, this gap is a critical architectural decision point. The gap is closing, but slowly.
Starbucks: Built their PWA to let customers browse the menu and customize orders without reliable internet. The offline first design reached customers in areas with spotty connectivity. The PWA is 0.4% the size of the native app. Doubled daily active users among web visitors.
Twitter Lite: Reduced storage requirement to less than 5% of the native Android app. 75% increase in tweets sent. 20% decrease in bounce rates. Critical for emerging markets with data caps and older devices.
Pinterest: Time spent on platform increased 40%. User generated ad revenue jumped 44%. Core engagement up 60%.
Trivago: 150% increase in engagement. 97% increase in conversions. Users returned at least twice in 14 days after installation.
Development: PWAs cost 40 to 60% less than maintaining separate iOS and Android apps. Single codebase, single team, single deployment pipeline.
Distribution: No $99/year Apple Developer Program fee. No 15 to 30% commission on transactions. No app review delays.
Maintenance: Updates deploy instantly. No waiting for store approval. Fix bugs in production immediately.
PWAs are not about whether they are better than native. They are about whether they are good enough for your use case. Content apps, e commerce, news, productivity tools: PWAs often exceed requirements. Games, AR/VR, heavy hardware integration: native still wins.
To pass browser PWA audits and enable installation, you need three things:
// Minimal service worker (sw.js)
const CACHE_NAME = 'app-shell-v1';
const SHELL_ASSETS = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/icons/icon-192.png'
];
// Pre cache shell on install
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(SHELL_ASSETS))
.then(() => self.skipWaiting())
);
});
// Clean old caches on activate
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
)
).then(() => self.clients.claim())
);
});
// Cache first for shell, network first for API
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.pathname.startsWith('/api/')) {
// Network first for API calls
event.respondWith(
fetch(event.request)
.catch(() => caches.match('/offline.html'))
);
} else {
// Cache first for static assets
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
}
});
Next.js: Use next-pwa package or the built in PWA guide. Server side rendering ensures search engines index all content.
Vite: vite-plugin-pwa provides zero config PWA generation with Workbox integration.
Create React App: Service worker template included by default, though CRA is being deprecated in favor of frameworks.
Caching too much: Do not cache everything. Cache the shell and critical assets. Let dynamic content use network first strategies.
Ignoring updates: Users can run old versions indefinitely. Implement update notification UI. Consider skipWaiting() for critical fixes.
Assuming iOS parity: Test on real iOS devices. Safari has different storage limits, eviction policies, and notification behavior.
A PWA is not a checkbox you enable. It is an architectural pattern combining service workers, caching strategies, and web app manifests into a coherent offline first experience. The quality depends entirely on your implementation.
Everything flows through the service worker. It is a programmable network proxy that determines how your app handles requests, stores assets, and responds to push events. Master service worker lifecycle and caching strategies.
Android treats PWAs as first class citizens. iOS treats them as second class web apps with significant limitations. If your audience is iOS heavy, factor this into your architecture decision. The gap is narrowing but still substantial.
No App Store commissions. No review delays. Instant updates. SEO indexable. Shareable via URL. These distribution advantages often outweigh feature limitations for content apps, e commerce, and productivity tools.
PWAs excel for content first, reach focused, budget conscious projects. Native apps excel for performance critical, hardware dependent, store monetized products. Many companies run both: PWA for reach, native for power users.
Service worker behavior differs across browsers and platforms. iOS Safari has quirks that emulators miss. Storage limits, notification reliability, and offline behavior need real device testing.
Chrome, Edge, Firefox, and Safari all support service workers and Web App Manifest without flags. The era of browser fragmentation as a PWA blocker is largely over. Tooling has matured. The question is no longer "can we build a PWA" but "should we."
Starbucks, Twitter, Pinterest, Alibaba, Spotify, Telegram, and thousands of other companies run PWAs in production. The patterns are proven. The business impact is documented. PWAs are not experimental technology.