What Makes a Web App "Progressive"
A Progressive Web App is a website that has been engineered to behave like an installed application: it runs behind a service worker, ships a Web App Manifest so it can be added to a home screen, caches assets so it keeps working with a poor or absent connection, and can send push notifications through the browser's own push API. None of this depends on a native app store. It is HTML, CSS and JavaScript running under a stricter set of technical rules than a normal website. If any one of those pieces is missing - no manifest, no registered service worker, no HTTPS - the browser will not offer the install prompt and the "app" is just a website with a nicer icon.
We build PWAs for businesses that have an existing web presence but are losing users to slow load times, unreliable connectivity, or the friction of asking someone to download a native app just to check an order status or browse a catalogue. Field service tools, e-commerce storefronts, internal dashboards, delivery and logistics tracking, and content platforms are the categories where this pattern earns its cost most reliably.
Technical Approach We Actually Use
Service Worker and Caching Strategy
The service worker is the core of the build, and its caching strategy has to match the content type. We typically mix strategies rather than pick one for the whole app: cache-first for static assets (fonts, icons, CSS bundles), network-first with a cache fallback for content that changes often (product listings, order data), and stale-while-revalidate for anything in between where showing slightly old data instantly is better than a spinner. Getting this wrong is the most common reason PWAs feel buggy - stale prices, outdated stock counts, or a UI that never reflects the latest state because everything was cached too aggressively.
App Shell Architecture
We separate the app shell - navigation, layout, static UI chrome - from the dynamic content it displays. The shell is precached and loads instantly on repeat visits, even offline, while dynamic content is fetched and injected on top. This is what produces the "opens instantly" feel that differentiates a PWA from a normal page reload.
Web App Manifest and Install Behaviour
The manifest defines app name, icons at multiple resolutions, theme colour, display mode (standalone vs fullscreen vs minimal-ui) and start URL. We configure this deliberately rather than using framework defaults, because display mode and start URL directly affect how the app looks once installed and where it lands when reopened from a home screen icon.
Push Notifications
Where engagement matters - abandoned cart recovery, delivery status, appointment reminders - we wire up the Push API with a backend subscription store (endpoint, keys, expiration) and a notification permission flow that asks at a contextually sensible moment rather than on page load, which is the single biggest driver of permission denial rates.
Framework and Stack Decisions
We build PWAs on React, Vue, or plain vanilla JS with Workbox for service worker generation, depending on what the client already has. If there's an existing Angular, React or Next.js codebase, we retrofit PWA capability into it rather than rebuilding from scratch - adding the manifest, registering a service worker, auditing for HTTPS and fixing any render-blocking patterns that hurt Lighthouse scores. If we're starting fresh, Workbox's `generateSW` or `injectManifest` modes give us control over precaching rules without hand-writing cache logic for every route.
For data-heavy apps we pair the service worker cache with IndexedDB for structured offline storage - order history, form drafts, catalogue data - since the Cache API alone is meant for network requests/responses, not for querying structured records offline.
Where PWAs Fit and Where They Don't
PWAs are a strong fit when the core use case is content, transactions, or status-checking, and when the target audience is on Android or desktop, where install support and background sync are mature. On iOS, Safari's support for service workers and push has historically lagged and storage can be evicted more aggressively, so we scope iOS expectations honestly upfront rather than overpromising native-equivalent behaviour. If a project needs deep OS integration - Bluetooth, background GPS tracking, camera-heavy processing, or App Store discoverability as a distribution channel - we recommend native or a hybrid approach instead, and say so during scoping rather than after launch.
Performance Auditing and Delivery
Every build goes through Lighthouse and WebPageTest passes against the PWA checklist: fast first contentful paint, a valid manifest, a registered and controlling service worker, offline fallback pages instead of the browser's default error screen, and correct handling of the `beforeinstallprompt` event so install prompts don't ambush users unexpectedly. We also test the actual install flow on real Android and desktop Chrome/Edge devices, not just emulators, since install banners and update behaviour (the "new version available" prompt after a service worker update) vary in ways emulators don't always surface.
Update Handling
Service worker updates are notoriously easy to get wrong - users can get stuck on an old cached version indefinitely if the update lifecycle isn't handled. We implement explicit update detection (`skipWaiting` and `clients.claim` used deliberately, not blindly) with a user-facing "refresh to update" prompt so cached content doesn't silently go stale.
Typical Deliverables
- Web App Manifest configured with icon sets, display mode, and theme metadata
- Service worker with a caching strategy matched to content type, built via Workbox or hand-rolled
- Offline fallback pages and offline-capable core flows (browsing, form drafts, cached data views)
- Push notification infrastructure including subscription storage and permission-flow design
- Lighthouse-audited performance baseline and installability report
- Update-handling logic so installed users receive new versions reliably
How We Scope a PWA Project
We start by looking at what already exists - a live site, an existing app, or neither - and what's actually broken: slow repeat visits, no offline behaviour, low mobile engagement, or the cost of maintaining separate iOS and Android codebases. From there we decide whether this is a retrofit onto an existing frontend or a new build, which caching strategy fits each screen, and whether push notifications and offline data sync are worth the added backend complexity for the specific use case. That scoping conversation, done honestly, is what keeps a PWA from becoming an expensive layer of complexity with no measurable benefit.