MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr πŸ”₯ tagline

Hey there πŸ‘‹ I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 β€” present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 β€” Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms β€” earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

PWAs vs Native Apps in 2022: Making the Right Choice

Compare PWAs and native apps: capabilities, limitations, when to choose each.

PWAMobileArchitectureFrontend

By MinhVo

Introduction

The decision between building a Progressive Web App and a native mobile application is one of the most consequential architectural choices a product team makes. This decision affects development cost, time to market, user experience, feature capabilities, maintenance burden, and distribution strategy for years to come. In 2022, the gap between PWAs and native apps has narrowed significantly β€” PWAs now support push notifications on iOS, install seamlessly on Android and desktop, and deliver offline experiences that rival native apps. Yet native applications still dominate in areas like advanced hardware access, background processing, and app store discovery.

The right choice depends on your product requirements, target audience, team capabilities, and business model. A content-focused application like a news site or blog benefits enormously from PWA's instant loading and offline support without the overhead of app store distribution. A fitness tracker that needs Bluetooth access to heart rate monitors and GPS tracking during background operation requires native capabilities. Many successful products build both β€” a PWA for web discovery and quick access, and a native app for power users who need deep device integration.

This guide provides a comprehensive, honest comparison of PWAs and native apps across every dimension that matters for product decisions, with specific technical details about what each platform can and cannot do, real-world case studies, and a decision framework to help you choose the right approach for your specific situation.

PWA vs Native comparison

Understanding the Platforms: Core Concepts

What Defines a PWA

A Progressive Web App is a web application that uses modern web APIs β€” service workers, web app manifest, and HTTPS β€” to deliver app-like experiences. PWAs are built with HTML, CSS, and JavaScript, run inside the browser's rendering engine, and are distributed via URL rather than app stores. The "progressive" nature means they work for every user regardless of browser or device, enhancing from a basic web page to a full app experience as browser capabilities allow.

Key technical characteristics of PWAs:

  • Installation: Browser prompt or manual "Add to Home Screen"
  • Offline: Service worker caches assets and data
  • Push notifications: Web Push API (Android fully supported, iOS 16.4+)
  • Hardware access: Camera, geolocation, accelerometer, limited Bluetooth
  • Background processing: Limited to Background Sync and Periodic Sync
  • Updates: Instant β€” users always get the latest version on next visit

What Defines a Native App

Native applications are built with platform-specific languages and APIs β€” Swift/Objective-C for iOS, Kotlin/Java for Android. They compile to machine code, have direct access to all device hardware and OS features, and are distributed through the Apple App Store and Google Play Store.

Key technical characteristics of native apps:

  • Installation: App store download, 10-200MB+ package
  • Offline: Full filesystem access, local databases, offline-first by default
  • Push notifications: Full native push with rich media, actions, and scheduled delivery
  • Hardware access: Bluetooth, NFC, USB, ARKit/ARCore, HealthKit, Wallet
  • Background processing: Background fetch, location updates, audio playback, VoIP
  • Updates: App store review process, user-initiated or auto-update

The Capability Gap in 2022

The gap between PWAs and native apps has narrowed considerably. Service workers provide robust offline support. The Web Push API now works on iOS 16.4. The File System Access API enables local file operations. Web Bluetooth enables communication with BLE devices. WebGPU brings high-performance graphics. However, significant gaps remain in background processing depth, hardware integration breadth, and OS-level integration.

Platform capabilities comparison

Architecture and Design Patterns

PWA Architecture

A PWA's architecture centers on the service worker as a programmable network proxy. The application shell pattern separates the static UI chrome from dynamic content, enabling instant rendering from cache while content loads from the network.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Browser                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚        Application Shell          β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”‚
β”‚  β”‚  β”‚  HTML    β”‚  β”‚   CSS        β”‚   β”‚   β”‚
β”‚  β”‚  β”‚  Shell   β”‚  β”‚   Styles     β”‚   β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”‚
β”‚  β”‚  β”‚  JS      β”‚  β”‚  Service     β”‚   β”‚   β”‚
β”‚  β”‚  β”‚  App     β”‚  β”‚  Worker      β”‚   β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                          β”‚               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  IndexedDB     β”‚  β”‚ Cache Storageβ”‚   β”‚
β”‚  β”‚  (data)        β”‚  β”‚ (responses)  β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Native App Architecture

Native apps follow platform-specific architectural patterns β€” MVVM for Android, MVC/MVVM for SwiftUI, or Clean Architecture for both. The entire application binary runs on the device with full access to platform APIs.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Operating System               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚        Native App Binary          β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”‚
β”‚  β”‚  β”‚  UI      β”‚  β”‚  Business    β”‚   β”‚   β”‚
β”‚  β”‚  β”‚  Layer   β”‚  β”‚  Logic       β”‚   β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”‚
β”‚  β”‚  β”‚  Data    β”‚  β”‚  Platform    β”‚   β”‚   β”‚
β”‚  β”‚  β”‚  Layer   β”‚  β”‚  APIs        β”‚   β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Hardware: GPS, Camera, BLE,     β”‚   β”‚
β”‚  β”‚  NFC, Accelerometer, Gyroscope   β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Hybrid Architecture: Best of Both Worlds

Many companies adopt a hybrid strategy β€” a PWA for web users and a native wrapper (Capacitor, TWA) for app store distribution. This approach maximizes reach while leveraging platform-specific capabilities where needed.

Step-by-Step Implementation

Building a Feature Comparison Matrix

Before making a platform decision, create a detailed feature matrix that maps every product requirement to platform capabilities.

interface PlatformCapability {
  feature: string;
  pwaSupport: 'full' | 'partial' | 'none';
  nativeSupport: 'full' | 'partial' | 'none';
  pwaNotes?: string;
  nativeNotes?: string;
  priority: 'critical' | 'important' | 'nice-to-have';
}
 
const featureMatrix: PlatformCapability[] = [
  {
    feature: 'Push Notifications',
    pwaSupport: 'partial',
    nativeSupport: 'full',
    pwaNotes: 'iOS 16.4+ only; limited rich notifications',
    nativeNotes: 'Full support with actions, media, scheduled delivery',
    priority: 'critical',
  },
  {
    feature: 'Offline Mode',
    pwaSupport: 'full',
    nativeSupport: 'full',
    pwaNotes: 'Service worker + IndexedDB; size limits apply',
    nativeNotes: 'Full filesystem access',
    priority: 'critical',
  },
  {
    feature: 'Camera Access',
    pwaSupport: 'full',
    nativeSupport: 'full',
    pwaNotes: 'MediaDevices API; no advanced controls',
    nativeNotes: 'Full manual controls, RAW capture',
    priority: 'important',
  },
  {
    feature: 'Bluetooth',
    pwaSupport: 'partial',
    nativeSupport: 'full',
    pwaNotes: 'Web Bluetooth API; Chromium only; no classic BT',
    nativeNotes: 'Full BLE and classic Bluetooth',
    priority: 'nice-to-have',
  },
  {
    feature: 'Background Location',
    pwaSupport: 'none',
    nativeSupport: 'full',
    pwaNotes: 'Only available when app is in foreground',
    nativeNotes: 'Continuous background tracking with geofencing',
    priority: 'critical',
  },
  {
    feature: 'App Store Discovery',
    pwaSupport: 'none',
    nativeSupport: 'full',
    pwaNotes: 'No app store presence; SEO-dependent',
    nativeNotes: 'App Store and Google Play listing',
    priority: 'important',
  },
];

Implementing a PWA with Capacitor Fallback

For features that require native capabilities, use Capacitor to wrap your PWA in a native shell while maintaining the web codebase.

// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
 
const config: CapacitorConfig = {
  appId: 'com.example.myapp',
  appName: 'MyApp',
  webDir: 'dist',
  plugins: {
    PushNotifications: {
      presentationOptions: ['badge', 'sound', 'alert'],
    },
    LocalNotifications: {
      smallIcon: 'ic_notification',
      iconColor: '#3b82f6',
    },
  },
};
 
export default config;
 
// Conditional native feature loading
import { Capacitor } from '@capacitor/core';
 
async function initializeNotifications() {
  if (Capacitor.isNativePlatform()) {
    // Use native push notifications
    const { PushNotifications } = await import('@capacitor/push-notifications');
    const permission = await PushNotifications.requestPermissions();
    if (permission.receive === 'granted') {
      await PushNotifications.register();
    }
  } else if ('serviceWorker' in navigator && 'PushManager' in window) {
    // Use web push
    const registration = await navigator.serviceWorker.ready;
    const subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: VAPID_KEY,
    });
    await registerSubscription(subscription);
  }
}

Measuring PWA vs Native Performance

// Performance measurement for platform comparison
const metrics = {
  // Time to interactive
  tti: performance.now(),
 
  // First contentful paint
  fcp: 0,
 
  // Largest contentful paint
  lcp: 0,
};
 
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'first-contentful-paint') {
      metrics.fcp = entry.startTime;
    }
  }
}).observe({ type: 'paint', buffered: true });
 
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  metrics.lcp = entries[entries.length - 1].startTime;
}).observe({ type: 'largest-contentful-paint', buffered: true });

Platform decision framework

Real-World Use Cases

Use Case 1: E-Commerce β€” Choose PWA

An online retailer with a mobile-first audience chose a PWA over native apps. The PWA loaded in under 2 seconds on 3G networks, supported offline product browsing, and sent push notifications for order updates and abandoned carts. Development cost was 40% less than building separate iOS and Android apps, and the single codebase eliminated the iOS/Android feature parity problem. Conversion rates increased by 76% compared to the previous mobile website.

Use Case 2: Fitness Tracking β€” Choose Native

A fitness startup needed continuous GPS tracking during runs, heart rate monitoring via Bluetooth sensors, and background audio playback β€” all simultaneously. PWAs cannot maintain background GPS tracking, and Bluetooth support is limited to Chromium browsers on desktop. Native development was the only option that met the product's core requirements.

Use Case 3: News Platform β€” Choose PWA

A news organization launched a PWA that pre-cached the latest articles during installation, provided offline reading, and sent breaking news push notifications. The PWA reduced page load time from 5.8 seconds to 1.7 seconds, increased session duration by 65%, and reduced bounce rate by 43%. The app store distribution of native apps was unnecessary since the organization already had strong web traffic and SEO presence.

Use Case 4: Banking App β€” Choose Both

A bank built a PWA for quick account balance checks, transaction history, and bill payments β€” tasks that benefit from instant loading and no installation friction. A separate native app provides advanced features like biometric authentication, secure document scanning, and in-app check deposit that require native security APIs. The PWA serves as a lightweight entry point that converts users to the native app.

Best Practices for Production

  1. Start with a PWA unless you have specific native requirements: PWAs are cheaper to build, easier to maintain, and work across all platforms. Only choose native when you have confirmed that the required features are impossible or impractical on the web platform.

  2. Use feature detection, not platform detection: Check for specific API availability ('serviceWorker' in navigator, 'PushManager' in window) rather than assuming capabilities based on the user agent string. Browser capabilities change frequently.

  3. Design for the weakest platform first: If building both a PWA and native app, design the core experience for the PWA's capabilities. This ensures the web experience is excellent rather than a watered-down version of the native app.

  4. Measure actual user behavior: Track installation rates, session lengths, feature usage, and conversion rates on both platforms. Data often reveals that users prefer the friction-free access of a PWA over the commitment of downloading a native app.

  5. Consider the maintenance cost: A native app requires maintaining two codebases (iOS and Android), dealing with app store review processes, and supporting older OS versions. A PWA updates instantly for all users with no review delays.

  6. Plan for app store distribution with Capacitor or TWA: If app store presence is important for discovery, wrap your PWA with Capacitor (iOS + Android) or a Trusted Web Activity (Android only). This gives you app store listing without maintaining a separate native codebase.

  7. Implement analytics for both platforms: Track the same KPIs across PWA and native to make data-driven decisions about where to invest development resources. Compare user acquisition cost, lifetime value, and engagement metrics.

  8. Test on real devices under real conditions: PWAs behave differently on low-end Android devices versus flagship phones. Service worker caching strategies that work on fast connections may need adjustment for 2G/3G networks common in emerging markets.

Common Pitfalls and Solutions

PitfallImpactSolution
Choosing native for features PWAs already supportUnnecessary development cost and longer time to marketAudit requirements against current PWA capabilities before deciding
PWA with poor offline experienceUsers lose trust when the app fails without connectivityImplement robust caching strategies and offline fallbacks for every page
Ignoring iOS PWA limitationsFeatures work on Android but fail on iPhoneTest on both platforms; implement graceful degradation for iOS limitations
Native app that nobody downloadsHigh development cost with low adoptionValidate demand with a PWA first; use app store optimization if going native
Maintaining separate codebases with divergent featuresiOS users get features Android users don't (and vice versa)Use cross-platform frameworks or a shared PWA codebase with Capacitor
Not accounting for app store review delaysCritical bug fixes take days to reach usersPWAs update instantly; for native, use phased rollouts and thorough testing

Performance Optimization

PWA Performance Techniques

PWAs can match or exceed native app performance through aggressive caching, preloading, and code splitting. The key advantage is that PWAs load instantly on repeat visits because the application shell is served from the service worker cache.

// Preload critical routes
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll([
        '/',
        '/dashboard',
        '/profile',
        '/static/js/main.js',
        '/static/css/main.css',
      ]);
    })
  );
});

Native Performance Techniques

Native apps achieve peak performance through ahead-of-time compilation, hardware-accelerated UI rendering, and direct memory management. The key advantage is consistent frame rates during complex animations and transitions.

Comparison with Alternatives

FactorPWANative iOSNative AndroidReact NativeFlutter
Development Time1x (single codebase)2-3x2-3x1.5x1.5x
Maintenance CostLowHighHighMediumMedium
UI PerformanceGoodExcellentExcellentGoodVery Good
Hardware AccessPartialFullFullMost via bridgesMost via plugins
Offline CapabilityService workersFullFullFullFull
App Size0 (browser)10-100MB10-100MB15-40MB5-20MB
Instant UpdatesYesNo (review)No (review)OTA via CodePushOTA possible
User AcquisitionSEO, URL sharingApp StoreGoogle PlayBoth storesBoth stores
Push NotificationsLimited (iOS)FullFullFullFull

Advanced Patterns

Progressive Enhancement Strategy

Build the core experience as a PWA, then layer native capabilities for users on capable platforms. This maximizes reach while providing the best possible experience on each device.

class PlatformAdapter {
  private capabilities: Map<string, boolean> = new Map();
 
  async initialize() {
    this.capabilities.set('push', await this.checkPushSupport());
    this.capabilities.set('bluetooth', await this.checkBluetoothSupport());
    this.capabilities.set('background-sync', await this.checkBackgroundSync());
    this.capabilities.set('share-target', await this.checkShareTarget());
  }
 
  has(feature: string): boolean {
    return this.capabilities.get(feature) ?? false;
  }
 
  private async checkPushSupport(): Promise<boolean> {
    return 'PushManager' in window && 'serviceWorker' in navigator;
  }
 
  private async checkBluetoothSupport(): Promise<boolean> {
    return 'bluetooth' in navigator;
  }
}

TWA (Trusted Web Activity) for Google Play

A TWA wraps your PWA in a Chrome Custom Tab, allowing it to be listed on the Google Play Store while maintaining the single web codebase. Users install from the Play Store, and the app runs using Chrome's rendering engine with full PWA capabilities.

Testing Strategies

Cross-Platform Testing Matrix

Test on the following minimum matrix to ensure broad compatibility:

PlatformBrowserVersionService WorkersPushInstall
AndroidChrome90+FullFullFull
iOSSafari16.4+FullPartialPartial
WindowsChrome90+FullFullFull
macOSSafari16+FullPartialPartial
WindowsEdge90+FullFullFull

Automated PWA Compliance Testing

# Run Lighthouse PWA audit
npx lighthouse https://your-app.com --only-categories=pwa --output=json
 
# Check specific PWA criteria
npx lighthouse https://your-app.com --only-categories=pwa \
  --chrome-flags="--headless" \
  --output=json | jq '.audits | to_entries[] | select(.value.score == 0)'

Future Outlook

The capability gap between PWAs and native apps continues to shrink. Apple's incremental improvements to Safari β€” including Web Push notifications, improved service worker support, and better PWA installation flows β€” suggest a long-term convergence toward platform parity. The Web Platform Incubator Community Group (WICG) continues to ship APIs that bring native-like capabilities to the web.

For most applications, the decision is shifting toward PWAs as the default choice, with native reserved for products that require deep hardware integration or advanced background processing. The cost advantage, instant deployment, universal reach, and improving capabilities make PWAs the pragmatic choice for the majority of new product development.

Conclusion

The PWA vs native decision is not binary β€” it is a spectrum of trade-offs between development cost, user experience, feature capabilities, and distribution strategy. In 2022, PWAs have reached a maturity level where they are the right choice for most applications, with native development reserved for products that genuinely require capabilities the web platform cannot provide.

Key takeaways from this guide:

  1. Default to PWA unless you have specific native requirements β€” the cost, maintenance, and distribution advantages are substantial for most products.
  2. Build a feature matrix mapping every product requirement to platform capabilities before making a decision.
  3. The hybrid approach works β€” use Capacitor or TWA to get app store distribution with a PWA codebase.
  4. Test on real devices on both Android and iOS to understand platform-specific limitations.
  5. Measure and iterate β€” track user behavior on both platforms and let data drive platform investment decisions.

Start by building your core product as a PWA. Validate the product with real users, identify any native capabilities that are genuinely blocking features, and only then invest in native development for those specific capabilities. The web.dev PWA documentation, Capacitor documentation, and MDN Web APIs provide comprehensive resources for building cross-platform web applications.