Capacitor, PWAs, and the Cross-Platform Web Stack
The web has been the most reliably cross-platform UI runtime for thirty years. Every iPhone, Android phone, desktop OS, and embedded device ships a usable browser engine. The category of frameworks that exploit this — wrapping web content in a native shell, or installing web apps directly via PWA standards, or replacing Electron with system-WebView-based desktop runtimes — has matured dramatically since 2020. Capacitor 6+ replaced Cordova as the Ionic team’s bridge. Tauri 2 brought Rust-based desktop + mobile apps under a single workflow with native WebView and ~10 MB output. PWA capabilities expanded with WebGPU, WebAuthn, WebUSB, OPFS, and the broader Project Fugu surface, even as iOS Safari has remained the laggard. .NET MAUI replaced the discontinued Xamarin in 2024. This note covers the PWA platform-API surface, the native-wrapper alternatives, the case studies (Twitter Lite, Pinterest Lite, Starbucks PWA, Figma web, Photopea), the distribution gaps (App Store, Play Store, Microsoft Store), and the decision matrix for choosing between RN, Flutter, Capacitor, Tauri, and MAUI.
See also
- networking-foundations — HTTP, WebSockets, service workers as a fetch interceptor.
- http2-http3-quic — protocol features service workers depend on.
- auth-authz — WebAuthn, OAuth, Sign in with Apple in a PWA.
- cryptography-fundamentals — Web Crypto API, subtle.crypto.
- observability-stack — RUM (Real User Monitoring) and Core Web Vitals.
- concurrency-primitives — Web Workers, SharedArrayBuffer.
- cpu-cache-performance — browser JIT and rendering pipeline performance.
- sql-nosql-design — IndexedDB and OPFS as storage primitives.
1. Progressive Web Apps in 2026
1.1 The PWA minimum
A PWA is a regular website that meets four criteria:
- Served over HTTPS.
- Has a Web App Manifest (
manifest.json) describing name, icons, theme colour, display mode, start URL. - Has a Service Worker registered for offline / push / background sync.
- Passes basic installability checks (icon sizes, manifest validity).
Browser support for “Install” prompts: Chrome (desktop + Android), Edge, Samsung Internet, Firefox (desktop, partial on mobile). Safari iOS supports installation via “Add to Home Screen” but with a long list of caveats (Section 1.6).
1.2 manifest.json
{
"name": "Acme",
"short_name": "Acme",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable any" }
],
"start_url": "/?source=pwa",
"display": "standalone",
"theme_color": "#0a0a0a",
"background_color": "#ffffff",
"id": "/",
"scope": "/",
"shortcuts": [...]
}Maskable icons (with safe zones) are required for Android adaptive icons; the display: "standalone" mode removes browser chrome. Modern additions: display_override (with window-controls-overlay, tabbed, minimal-ui, fullscreen), share_target (receive shared content), protocol_handlers (register custom URL schemes), file_handlers (open file types).
1.3 Service workers
A service worker is a JS context that runs separately from page contexts, intercepts network requests within its scope, and runs even when the page is closed. Lifecycle:
- Install. First time the SW is encountered. Pre-cache critical assets in
Cache Storage. - Activate. New SW takes over from old one.
clients.claim()makes it controlling immediately. - Fetch. Intercept requests; serve from cache / network / both.
- Push, Sync, Periodic Sync. Background events.
self.addEventListener('install', e => {
e.waitUntil(caches.open('v1').then(c => c.addAll(['/', '/app.js', '/style.css'])));
});
self.addEventListener('fetch', e => {
e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)));
});Skip-waiting + clients.claim. New SWs by default wait for all tabs to close before activating. self.skipWaiting() in install and clients.claim() in activate force immediate takeover; useful for rapid update flows but risks loading mismatched assets during transition.
Scope. A SW controls all pages under its registration path. /app/sw.js controls /app/* only; /sw.js at the root controls everything.
Update. Browsers re-check the SW script bytes every 24 hours and on each page navigation. A byte-level diff triggers install. Force update via registration.update().
1.4 Workbox
Google’s Workbox library wraps SW patterns:
precacheAndRoute(manifest)— version-aware precaching from a build manifest.registerRoute(matcher, strategy)— cache-first, network-first, stale-while-revalidate, network-only, cache-only strategies.BackgroundSyncPlugin,BroadcastUpdatePlugin,ExpirationPlugin.
Vite, Next.js, SvelteKit, Nuxt all ship Workbox-based PWA integrations (next-pwa, @vite-pwa/...).
1.5 Storage APIs
- Cache Storage. SW-controlled key/value of
Request/Responsepairs. Used for HTTP-resource caching. - IndexedDB. Key-object database. Async, transaction-scoped. The high-level libraries (Dexie, idb-keyval, RxDB) make it usable.
- localStorage / sessionStorage. Synchronous string KV; 5 MB quota; legacy, but ubiquitous.
- OPFS (Origin Private File System). Per-origin filesystem with synchronous I/O (in worker context). Backed by browser’s native filesystem; not exposed to user file manager. SQLite-in-the-browser builds (sqlite-wasm, absurd-sql) use OPFS for persistent multi-MB databases.
- File System Access API. Read/write to user-chosen filesystem locations. Chrome/Edge only; Firefox + Safari WebKit declined to implement citing privacy concerns.
1.6 The iOS Safari gap
Safari on iOS implements PWAs partially:
- Install via “Add to Home Screen.” Works since iOS 11.3 (2018).
- Service workers. Supported since iOS 11.3 but with stricter resource limits (50 MB cache quota historically; expanded to several GB in iOS 16+).
- Push notifications. Added in iOS 16.4 (March 2023) for home-screen-installed PWAs only — not Safari tabs. Web Push API + APNs gateway.
- No
beforeinstallprompt. iOS has no programmatic install prompt; users must manually use Share → Add to Home Screen. - WebGPU. Enabled in iOS 18 (September 2024); a year behind desktop Safari Tech Preview.
- App Lifecycle limits. Home-screen PWAs are killed aggressively; state must be persisted carefully.
- EU DMA changes (iOS 17.4, March 2024). Apple initially attempted to ship iOS 17.4 in the EU without Home Screen PWA support (replacing them with bookmark-shortcuts). After community + regulator pushback, restored within the same point release.
1.7 Android install banner and TWA
Chrome on Android prompts installation when engagement heuristics are met. The beforeinstallprompt event lets the page defer and trigger later. Trusted Web Activities (TWAs) wrap a PWA as a full Android app distributable through Play Store — see Section 5.
2. Project Fugu and the modern web platform
Project Fugu (Google + Microsoft + Intel) is the umbrella for shipping native-equivalent APIs to the web. Notable additions (most are Chromium-first; Firefox and Safari implement subset):
- Web Bluetooth. Communicate with BLE devices.
- Web USB. Communicate with USB devices.
- Web Serial. RS-232 serial communication.
- Web HID. Human Interface Devices (game controllers, custom hardware).
- Web NFC. Read/write NFC tags (Android Chrome only).
- WebAuthn. Passkeys, FIDO2 hardware keys. Universal (all browsers).
- WebRTC. Real-time audio/video/data peer-to-peer. Universal.
- WebGPU. GPU compute and rendering. Chromium, Safari 18+, Firefox Nightly.
- WebNN. Neural network acceleration via OS-level ML APIs (DirectML, CoreML, NNAPI). Behind flag.
- Web Codecs. Low-level audio/video encode/decode.
- Web Share / Share Target. Native share-sheet integration.
- Contact Picker. Read contacts (Android only).
- Idle Detection.
- Window Controls Overlay. Customise the title bar area in installed PWAs.
- File Handling. Register PWA as default for file types.
- Background Fetch. Long-running uploads/downloads that continue after page close.
3. Capacitor 6+
3.1 What Capacitor is
Capacitor is the Ionic team’s native runtime for web apps. Replaces Cordova (which the team open-sourced and forked from in 2018-19; Cordova entered Apache Attic 2024). Capacitor wraps a web view (WKWebView on iOS, WebView on Android, Edge WebView2 on Windows, system WebView on Electron desktop) and exposes a TypeScript plugin API for native capabilities.
Version 6 (April 2024) added:
- iOS 17 minimum.
- Android 14 SDK target.
- Updated default WebViews.
- Improved live-reload, hot module replacement.
Version 7 (late 2024 / 2025) added per-plugin Swift Package Manager support and unified Android Gradle Plugin 8.x integration.
3.2 Plugin ecosystem
100+ official plugins maintained by the Ionic team: Camera, Geolocation, Filesystem, Push Notifications, Local Notifications, Network, Device, App, Browser, Clipboard, Dialog, Haptics, Keyboard, Preferences, Share, Splash Screen, Status Bar, Toast, Action Sheet, Background Runner. Community plugins for everything else (Stripe, Firebase, AdMob, OneSignal, Capacitor Community projects).
Custom plugins authored in Swift/Kotlin via simple decorators:
@objc(MyPlugin)
public class MyPlugin: CAPPlugin {
@objc func echo(_ call: CAPPluginCall) {
let value = call.getString("value") ?? ""
call.resolve(["value": value])
}
}3.3 Build and deployment
Capacitor projects have ios/ and android/ directories with full Xcode/Android Studio projects. Standard tooling (xcodebuild, gradle, fastlane) applies. Build the web app first (npm run build); npx cap sync copies into native projects; build natively.
Ionic Appflow provides hosted CI + Live Updates (OTA bundle updates similar to EAS Update). Alternative: Capgo, Capawesome Cloud, custom CDN.
3.4 Capacitor Live Updates
Ionic Appflow (Live Updates feature) lets you push a new web-bundle (HTML/JS/CSS) to installed apps without app-store re-review. Subject to App Review policies — JS-only logic changes are fine; new native module entry points are not.
3.5 Use cases
Capacitor’s sweet spot: teams with a working web app (React, Vue, Angular, vanilla) that want to ship to iOS + Android without rewriting. BurgerKing, FedEx, MarketWatch, Sworkit, Diesel, Sonos, Calxa use Capacitor in production. The output looks identical across platforms (web-defined UI); native fidelity is achieved by responsive design and platform-specific styling.
4. Ionic Framework v8+
Ionic Framework is the component library + design system that often pairs with Capacitor. Built on Web Components (Stencil compiler). Provides ~100 UI components (ion-button, ion-card, ion-tabs, ion-modal) with iOS and Material Design modes that switch automatically based on platform.
Framework bindings:
@ionic/angular(most mature, since 2013).@ionic/react(added 2019).@ionic/vue(added 2020).- Vanilla JS / web components (always supported).
v8 (mid-2024) added new components (ion-input rewrite, ion-picker rewrite), CSS-only utility class system, dark mode improvements.
Note: Ionic + Capacitor is the canonical pairing but they are separable. Capacitor works with any web framework (Next.js, SvelteKit, Quasar, plain HTML); Ionic components work in any framework with or without Capacitor (Ionic in a pure PWA without native wrapping is common).
5. Trusted Web Activities (Play Store distribution)
A TWA is an Android activity that hosts a Chrome Custom Tabs-style full-screen Chrome session of a verified PWA. The PWA must host a assetlinks.json proving ownership of the package name. Wrap with PWABuilder (Microsoft), Bubblewrap (Google), or manually with the androidx.browser.trusted library.
Benefits:
- Single codebase (PWA) deployable to Play Store.
- Native splash screen, full-screen.
- Push notifications via Web Push.
- Distribution via Play Console (subject to standard policy review).
Limitations:
- Requires Chrome (or Chromium derivative supporting Trusted Web Activities) installed on device.
- No access to Android-specific native APIs beyond what the Web platform exposes.
- Play Store still requires standard policy compliance (privacy form, target SDK, etc.).
Production examples: Twitter Lite, Pinterest Lite, Starbucks Lite, Tinder Lite, Yahoo News, Spotify (partial), Hulu (some markets), Trivago. TWA category remains popular for “lite” variants targeting emerging-market devices with limited storage.
6. PWABuilder and Microsoft Store
Microsoft’s PWABuilder (pwabuilder.com) generates installable packages for:
- Google Play (TWA).
- Microsoft Store (PWA). Direct PWA submission with no native wrapping. Microsoft accepts standalone PWAs since 2018.
- iOS (Capacitor wrap). Wraps the PWA in a Capacitor project.
- macOS (Mac Catalyst).
PWA on Microsoft Store: full Win32 integration via WebView2; runs as native Windows app; access to most modern web APIs. Reasonable distribution channel for B2B web apps.
7. Tauri 2.0+
7.1 Architecture
Tauri (Tauri Apps, foundation under Linux Foundation Europe since 2024) is a Rust-based framework for desktop + mobile apps using the system WebView:
- iOS, macOS: WKWebView (Safari engine).
- Android: WebView (Chromium-based).
- Windows: WebView2 (Edge Chromium).
- Linux: WebKitGTK.
Frontend: any web framework (React, Vue, Svelte, Solid, vanilla). Backend: Rust application code communicating via an IPC layer.
7.2 Tauri 2 (released October 2024)
Major release adding:
- Mobile targets. iOS and Android, first stable mobile release.
- Plugin system overhaul. Reusable plugins across desktop + mobile.
- Permission system. Per-plugin capability allowlist, enforced at runtime.
- Smaller bundles. Hello-world ~10 MB on Windows vs Electron’s ~100 MB.
7.3 Compared to Electron
- Bundle size. Tauri ~10 MB vs Electron ~100 MB.
- Memory. Tauri ~30-50 MB resident vs Electron ~200 MB.
- Backend language. Tauri Rust vs Electron Node.js.
- WebView consistency. Tauri uses system WebView (different engine per OS — WebKit on macOS, Chromium on Windows, WebKitGTK on Linux); Electron bundles Chromium (consistent everywhere).
The WebView-engine difference is the central trade-off. Electron’s bundled Chromium guarantees identical rendering and API surface. Tauri’s system WebView delivers smaller bundles but requires testing on each platform’s engine (and dealing with WebKitGTK on Linux being a notable version lag).
7.4 Use cases
Tauri’s audience: developers who want Electron-like productivity with smaller, faster output. Production users include 1Password (replacing Electron-based 1Password 7 with Tauri-based 1Password 8 in part), Tencent’s CodeWhisperer-derived tools, multiple cryptocurrency wallets, AppFlowy (open-source Notion alternative).
8. Other native-wrapper frameworks
8.1 NativeScript
NativeScript (originally Telerik, now community) renders to native UI components (UIView / android.view.View) from JavaScript or TypeScript, with Angular / Vue / React / Svelte bindings. Different from Capacitor (which uses WebView): NativeScript talks directly to UIKit and Android UI APIs. Mindshare has declined since 2020 with the rise of RN and Flutter; still active.
8.2 Cordova
Apache Cordova (formerly PhoneGap) is the original native-wrapper framework (2009+). Effectively deprecated: the Apache Cordova project moved to the Apache Attic in 2024. Capacitor is the recommended migration path for active Cordova projects.
8.3 Quasar Framework
Vue-based framework that compiles to PWA, Capacitor-wrapped mobile, Electron desktop, BEX (browser extension), and Cordova (legacy). One codebase, multiple targets. Used by teams already committed to Vue.
8.4 PhoneGap
Adobe-acquired Cordova distribution; Adobe announced end-of-life in 2020.
9. .NET MAUI
9.1 Replacing Xamarin
.NET MAUI (Multi-platform App UI) replaced Xamarin.Forms; Xamarin reached end-of-support May 1, 2024 (no more updates). MAUI ships with .NET 6+ (originally), now .NET 8+ (stable since November 2023) and .NET 9 (November 2024). C# 12, XAML markup, MVVM, hot reload.
9.2 Architecture
Single project targets iOS, Android, macOS (Catalyst), Windows (WinUI 3). Each platform renders to native controls (not WebView, not custom Skia like Flutter). MAUI Handlers (replacing Xamarin Renderers) bridge MAUI control types to per-platform native controls.
Blazor Hybrid: ASP.NET Core Blazor pages rendered in a MAUI WebView, allowing C# UI everywhere (similar to Capacitor but Microsoft-blessed and C#-native).
9.3 Production use
Enterprise + Microsoft-shop apps. Less hobbyist mindshare than Flutter or RN. Microsoft’s own apps (Teams mobile parts, some Surface apps) use MAUI selectively. Telerik, Syncfusion, and DevExpress sell commercial MAUI component libraries.
9.4 Compared to alternatives
- MAUI vs Flutter. MAUI uses native controls (consistent platform feel) vs Flutter’s custom rendering (consistent across platforms).
- MAUI vs RN. MAUI uses C# vs RN’s JavaScript; MAUI’s tooling is Visual Studio-centric.
- MAUI vs Xamarin. Direct replacement; same conceptual model with cleaner cross-platform APIs.
10. Cross-framework comparison matrix
| Dimension | React Native | Flutter | Capacitor | Tauri | MAUI |
|---|---|---|---|---|---|
| Frontend language | JS/TS | Dart | JS/TS (web) | JS/TS (web) | C# / XAML |
| Backend language | JS + native (Swift/Kotlin) | Dart + native | Web + native plugins | Rust + JS | C# |
| Rendering | Native widgets | Custom (Impeller) | WebView | WebView | Native widgets |
| iOS | Yes | Yes | Yes | Yes (Tauri 2) | Yes |
| Android | Yes | Yes | Yes | Yes (Tauri 2) | Yes |
| Web | Yes (Expo) | Yes (Wasm) | Yes (native PWA) | Yes (web first) | Blazor Hybrid |
| Desktop | Win/Mac (RN-Win/Mac) | Yes | Electron-via-Capacitor | Yes (first-class) | Win/Mac |
| Bundle size | ~20-40 MB | ~15-25 MB | ~5-15 MB | ~5-10 MB | ~20-40 MB |
| Memory | High | Medium | Medium | Low | Medium |
| Native feel | High | Medium | Medium (web-styled) | Web-styled | High |
| Animation perf | Good (Reanimated) | Excellent (Impeller) | Web-limited | Web-limited | Native |
| Hot reload | Yes | Yes (fastest) | Yes (web) | Yes (web) | Yes |
| First-app cost | Medium | Medium | Low | Low | Medium |
| Production users | Discord, Shopify | BMW, ByteDance | Burger King | 1Password | Microsoft Teams parts |
11. Performance: Core Web Vitals
For any web-based mobile app (PWA, Capacitor, TWA), the Core Web Vitals matter for both UX and SEO:
- LCP (Largest Contentful Paint). When the largest above-the-fold element renders. Target ≤2.5 s.
- INP (Interaction to Next Paint). Replaced FID in March 2024 as the responsiveness metric. Measures the time from user interaction to next paint after handler execution. Target ≤200 ms.
- CLS (Cumulative Layout Shift). Sum of unexpected layout shifts (without user input). Target ≤0.1.
PWA-specific optimisations:
- App-shell architecture: cache the chrome + navigation; lazy-load route content.
- Critical CSS inlining + async non-critical CSS.
- HTTP/3 + preconnect to third-party origins.
- Image responsive
srcset+ AVIF/WebP. link rel="preload"for fonts (alsofont-display: swapto avoid invisible text).- React Server Components / streaming SSR for instant first paint.
- Workbox precaching of build manifest.
12. Notable web-mobile case studies
12.1 Twitter Lite (2017)
Twitter’s first major PWA. <1 MB initial bundle for the entire app (vs 23 MB native iOS, 100+ MB native Android at the time). Cut data usage 70%; bounce rate dropped 20%; tweets sent up 75% in emerging markets. Now (post-rebranding to X) the web app remains React-based but the strict “Lite” branding has been dropped.
12.2 Pinterest Lite
Pinterest rewrote their mobile web app as a PWA in 2018; time-on-site +40%, login conversion +60%, ad revenue +44%.
12.3 Starbucks PWA
Starbucks’ ordering PWA (2017) was 99.84% smaller than the iOS app. Daily active users on the PWA doubled vs the previous mobile web app.
12.4 Tinder Lite (Tinder Online)
PWA version launched 2017. Session-time +30%, gameplay metrics improved across most cohorts.
12.5 Spotify TWA on Play Store
Spotify ships a TWA-based “Spotify Lite” in some markets; targets emerging-market devices with limited storage.
12.6 Microsoft Teams Web
Teams web client uses React + Fluent UI + WebRTC. Increasingly the recommended client over Teams desktop (which is itself Electron-based, transitioning to a WebView2-only “Teams 2.0” architecture as of 2023-24).
12.7 Figma Web
Figma’s editor runs in the browser via WebAssembly (C++ rendering core compiled to Wasm) with React + UI. Wasm + WebGL/WebGPU enables 60 FPS multi-MB designs. Native Figma desktop is an Electron wrapper of the same web app. The pattern (Wasm for compute, JS for UI) is increasingly common for design and creative tools.
12.8 Photopea
Photoshop-equivalent web app. Built by a single developer (Ivan Kutskir); pure JS + Canvas + Web Workers. Profitable; demonstrates the surface area achievable in a pure PWA.
12.9 Stadium-scale PWAs
Tesla in-vehicle UI is Chromium-based; large stadium scoreboard systems use WebRTC-streamed dashboards; many digital signage networks are PWAs running on cheap ARM SBCs.
13. WebAuthn and passkeys
WebAuthn enables phishing-resistant authentication. The browser API:
const credential = await navigator.credentials.create({
publicKey: {
challenge,
rp: { name: 'Acme', id: 'acme.com' },
user: { id, name: 'alice@acme.com', displayName: 'Alice' },
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
authenticatorSelection: { residentKey: 'required', userVerification: 'preferred' },
},
});Passkeys (Apple + Google + Microsoft 2022) are WebAuthn credentials synchronised across the user’s device cluster (iCloud Keychain, Google Password Manager, Microsoft Authenticator). PWAs can use them with no native code. See auth-authz for the protocol detail.
14. Service worker security
- Same-origin scope. SWs only control their origin; cross-origin requests pass through unmodified.
- HTTPS required. SWs cannot be registered over HTTP (localhost exception for dev).
- Update flow. Compromised SW persists until update is fetched and activated. Limit SW capabilities; never inject user-supplied JS into the SW context.
- Quota. Per-origin storage quota (typically several GB but browser-managed); LRU eviction when device storage low.
Background sync and periodic sync can run in the background even when the page is closed; design to handle revocation (user closes tab and never returns).
14b. Background sync and periodic sync
- Background Sync API.
registration.sync.register('upload-photo'); the SW receives asyncevent when connectivity returns. Useful for queued POST requests that survive page-close. Browser support: Chromium-only since 2016; Safari + Firefox declined to ship. - Periodic Background Sync.
registration.periodicSync.register('refresh-feed', { minInterval: 24*60*60*1000 }); the SW receives aperiodicsyncevent at the requested interval (heavily throttled by browser). Chromium-only. - Background Fetch API. Long-running uploads/downloads that survive page-close. The user sees a system notification (“Downloading…”); the SW receives
backgroundfetchsuccess/backgroundfetchfailureevents. Chromium-only.
The pattern: feature-detect, use where supported, fall back to in-tab-only logic elsewhere. Tools like Workbox’s BackgroundSyncPlugin handle the fallback automatically.
14c. PWA install metrics
Engagement tied to installation:
- Install rate. % of eligible users (engagement heuristics met) who install. Industry typical: 1-10% for consumer apps.
- DAU/MAU. Installed PWA users typically have 2-3× higher engagement than tab users.
- Push opt-in rate. Of installed users, what % grant notification permission. iOS push (16.4+) requires install first; conversion drops by half.
- Retention. D1, D7, D30 retention is generally lower for PWAs than native (no app store recommendation flywheel); higher than mobile web (return-from-icon is faster).
Analytics: standard web analytics (GA4, Plausible, PostHog, Fathom) cover PWAs; install vs in-tab segmentation via the display-mode: standalone media query or getInstalledRelatedApps() API.
14d. Edge runtimes and SSR for PWAs
Modern PWAs increasingly server-render to optimise LCP. Edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy, AWS Lambda@Edge) execute SSR close to the user; the SW caches the rendered HTML for subsequent navigation.
- Next.js (Vercel). App Router (Next.js 13+) with React Server Components. PWA manifest + service worker via next-pwa or @ducanh2912/next-pwa.
- SvelteKit. First-class PWA support via @vite-pwa/sveltekit; SSR by default.
- Nuxt 3. Vue equivalent; @vite-pwa/nuxt for PWA.
- Astro. Multi-framework SSR; service-worker plugin via @vite-pwa/astro.
- Remix. Web-fundamentals-first; SW via remix-pwa.
- Qwik. Resumability-based SSR; aggressive lazy-loading.
The pattern is mature: edge SSR for initial render, hydration to interactive PWA, service worker for offline, IndexedDB for client state.
14e. Capacitor Live Updates vs CodePush vs EAS
Three competing OTA-update systems for mobile apps shipping web/JS bundles:
- Capacitor Live Updates (Ionic Appflow). Ionic-hosted, paid. Channels (production, staging), rollback, A/B testing. Standard for Capacitor apps.
- CodePush (Microsoft App Center). Discontinued — App Center retires in 2026. Migrate off.
- EAS Update (Expo). For Expo / RN apps; see react-native-deep.
- Self-hosted (Capgo, Capawesome Cloud, custom S3). Roll your own; trades vendor lock-in for ops cost.
The categorical risk: Apple’s App Review tolerates JS bundle updates that preserve “advertised functionality” but rejects substantial new features delivered via OTA without re-review. Google Play is more permissive but still expects user-facing changes to flow through the standard track. Operational discipline: ship feature flags + small bundle deltas, not large reskins, via OTA.
15. When to choose what
Decision tree (rough heuristic):
- Pure web mobile app, no native APIs. PWA.
- Need Play Store / App Store distribution + most web APIs are enough. PWA → TWA for Play; PWABuilder for Microsoft Store; iOS remains the gap (PWABuilder or Capacitor wrap).
- Need significant native APIs (Bluetooth, deep camera, native payments, sensors). Capacitor.
- Cross-platform mobile + desktop, Rust comfortable, smaller bundle priority. Tauri.
- Cross-platform mobile + desktop with consistent fluid graphics. Flutter.
- React team, cross-platform mobile, web-second-priority. React Native + Expo.
- C# team, enterprise app, Windows-priority. .NET MAUI.
- Mobile-only, peak iOS / Android polish. Native (Swift/SwiftUI; Kotlin/Compose).
16. Common pitfalls
- iOS Safari PWA service worker eviction. Aggressive resource cleanup deletes the SW + caches under storage pressure. Persistent storage requires
navigator.storage.persist()(which iOS Safari may deny silently). - Capacitor plugin permissions on iOS. Forgetting to declare Info.plist usage strings causes silent denial; symptoms surface only on real device.
- Tauri WebKitGTK on Linux. Distros ship varying WebKitGTK versions; some block modern web APIs. Test on Ubuntu + Fedora as baselines.
- TWA verification breaks.
assetlinks.jsontypo (wrong package name, wrong SHA256 fingerprint) silently downgrades the TWA to Chrome Custom Tabs (still functional but with Chrome chrome). - MAUI hot reload flakiness on iOS Catalyst. Restart loops; known issue in .NET 8 SR releases.
- Service worker stale-cache shipping bugs. Cache-first strategies for
index.htmlstrand users on old shells indefinitely. Use network-first for HTML, cache-first for hashed assets. - PWA install prompt timing. Triggering
beforeinstallprompton first page load is ignored as spam; require some engagement signal. - Web push delivery on iOS. Requires home-screen install + iOS 16.4+; users in older iOS get no push.
Further reading
- web.dev — Project Fugu API status, PWA guides, Core Web Vitals.
- Workbox documentation (developer.chrome.com/docs/workbox).
- Capacitor docs (capacitorjs.com).
- Tauri docs (tauri.app).
- .NET MAUI docs (learn.microsoft.com/dotnet/maui).
- Building Progressive Web Apps — Tal Ater (O’Reilly 2017, dated but conceptually sound).
- Beyond the Web — Jen Looper (Smashing Magazine column on cross-platform).
- Maximiliano Firtman, firt.dev — long-form PWA + mobile web writing.
- Surma + Jake Archibald — Chrome team architectural talks (HTTP 203 podcast).