Back to articles
May 21, 2026

PWA — Progressively Enhanced Apps

PWA — Progressively Enhanced Apps Progressive Web Apps (PWAs) combine the best of web and native apps. They work offline, load quickly, can be installed on the home screen, and send push…

Placeholder cover imagePhoto: Lorem Picsum / Unsplash

PWA — Progressively Enhanced Apps

Progressive Web Apps (PWAs) combine the best of web and native apps. They work offline, load quickly, can be installed on the home screen, and send push notifications — all without an app store. This guide walks through building a PWA from scratch.

What Makes a PWA

A PWA must meet three core criteria:

  1. Secure: Served over HTTPS
  2. Installable: Has a web app manifest and meets browser requirements
  3. Offline-capable: Uses a service worker to cache resources

These features are progressive, meaning they enhance the experience for supporting browsers while degrading gracefully for older ones.

The Web App Manifest

The manifest is a JSON file that tells the browser how your app should behave when installed:

{
  "name": "My PWA App",
  "short_name": "MyPWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#2563eb",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Link it in your HTML:

<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2563eb">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

Service Worker Basics

The service worker is a JavaScript file that runs in the background, separate from the web page. It handles caching, push notifications, and background sync.

// sw.js — Service Worker

const CACHE_NAME = 'pwa-v1';
const ASSETS_TO_CACHE = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/app.js',
  '/icons/icon-192.png',
  '/icons/icon-512.png'
];

// Install event — cache assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS_TO_CACHE))
  );
  self.skipWaiting();
});

// Activate event — clean old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys
          .filter((key) => key !== CACHE_NAME)
          .map((key) => caches.delete(key))
      )
    )
  );
  self.clients.claim();
});

// Fetch event — serve from cache, fall back to network
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request).then((response) => {
        // Clone the response for caching
        const clone = response.clone();
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, clone);
        });
        return response;
      });
    })
  );
});

Register the service worker in your main JavaScript:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then((registration) => {
        console.log('SW registered:', registration.scope);
      })
      .catch((error) => {
        console.log('SW registration failed:', error);
      });
  });
}

Offline Experience

A great PWA doesn't just work offline — it tells the user when it does:

// Show offline banner
window.addEventListener('offline', () => {
  document.getElementById('offline-banner').style.display = 'block';
});

window.addEventListener('online', () => {
  document.getElementById('offline-banner').style.display = 'none';
  window.location.reload();
});
<div id="offline-banner" style="display:none; background:#f59e0b; color:#000; text-align:center; padding:0.5rem;">
  You are offline. Some features may be limited.
</div>

Push Notifications

Enable real-time engagement with push notifications:

// Request permission
async function requestNotificationPermission() {
  if ('Notification' in window && Notification.permission === 'default') {
    const permission = await Notification.requestPermission();
    if (permission === 'granted') {
      const subscription = await navigator.serviceWorker.ready
        .then(reg => reg.pushManager.subscribe({
          userVisibleOnly: true,
          applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
        }));
      console.log('Subscription:', subscription);
    }
  }
}

// Show notification
self.addEventListener('push', (event) => {
  const data = event.data?.json() || { title: 'Update', body: 'New content available' };
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      badge: '/icons/icon-192.png'
    })
  );
});

Conclusion

PWAs bridge the gap between web and native apps, offering installability, offline support, and push notifications without the friction of app stores. Start with the manifest and a basic service worker, then gradually add features like background sync and push notifications. Test with Lighthouse's PWA audit to ensure you're hitting all the criteria. The return on investment is significant — users love the app-like experience, and you maintain the reach and discoverability of the open web.

The Signal

AI-generated brief

Progressive Web Apps deliver native-level functionality—including offline access, home-screen installation, and push notifications—directly through the browser, eliminating app-store friction while preserving web reach.

Stance · BullishConfidence · Established

The article frames PWAs as a high-return, low-friction alternative to native development, emphasizing immediate UX benefits and streamlined distribution.

Key takeaways

  • PWAs rely on three non-negotiable foundations: HTTPS delivery, a compliant web app manifest, and a service worker for resource caching.
  • Implementing a service worker requires managing distinct lifecycle events (install, activate, fetch) to cache assets and route network requests efficiently.
  • Native-like user retention is achieved through straightforward integration of offline status banners and VAPID-based push notification subscriptions.
  • Development follows a progressive enhancement model, prioritizing core caching before layering in advanced capabilities like background sync.
  • Automated validation via tools like Lighthouse ensures consistent compliance across all required PWA criteria.

What to watch next

  • Lighthouse PWA audit threshold adjustments
  • Industry adoption rates for background sync implementations
  • Shifts in enterprise offline-first architecture standards

Who should care

Frontend developersProduct managersMobile engineering leadsSaaS founders

Key players

Service WorkersWeb App ManifestVAPID protocolGoogle LighthouseJavaScript runtime environments

Auto-generated from the article by our model — a reading aid, not a replacement for the piece.

The dispatch

One sharp read on the day’s biggest tech story.

Reported analysis for people who build software — free, most days, no spam.

Support our workIndependent, reader-funded tech journalism. If a piece helped you, chip in.Chip in →