smaple.tr
progressive web app

Progressive Web App Development: Service Workers, Offline, and Installability [2026]

Mehmet Kurtipek
March 16, 2026
10 min read
progressive web app
PWA
service worker
offline capability
web manifest
push notifications

Starbucks reduced their ordering app from 148 MB to 600 KB and matched native daily active user numbers. Twitter Lite decreased data consumption 70% and increased tweet volume 75%. Pinterest grew mobile ad revenue 44% and doubled time on site. These are not experimental results — they are production outcomes from progressive web app development implemented at scale.

The technical foundation is simpler than the business results suggest: a service worker intercepting network requests, a web app manifest declaring installability metadata, and HTTPS as the security foundation. Together, they transform a web application into something that behaves like a native app — installable from the browser, functional offline, capable of push notifications — without App Store review cycles or platform-specific codebases.

This guide covers the complete technical implementation: service worker lifecycle and caching strategies, manifest configuration, offline capability patterns, push notification integration, installability, performance optimization, and the decision framework for when PWA is and is not the right approach.

Progressive Web App Development: The Technical Foundation

Three components are required for a PWA to meet browser installability criteria:

  1. Service Worker: A JavaScript file running in a separate thread, acting as a network proxy
  2. Web App Manifest: A JSON file declaring the app's name, icons, theme colors, and display mode
  3. HTTPS: All resources must be served over HTTPS (localhost is exempt for development)

These three components work together. The service worker intercepts network requests and implements caching strategy. The manifest enables the "Add to Home Screen" prompt. HTTPS ensures the service worker cannot be hijacked to serve malicious content.

Service Worker Architecture

The service worker is the defining technical component of a PWA. Understanding its lifecycle is prerequisite to effective implementation.

Service Worker Lifecycle

Registration: The main JavaScript file registers the service worker:

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

The service worker file (sw.js) must be served from the root scope or a path that encompasses the pages it will control.

Installation: The browser downloads and parses the service worker. The install event fires. This is the correct place to pre-cache critical resources:

const CACHE_NAME = 'app-v1';
const PRECACHE_ASSETS = ['/index.html', '/app.css', '/app.js', '/offline.html'];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(PRECACHE_ASSETS))
  );
  self.skipWaiting(); // Activate immediately, don't wait for existing clients
});

Activation: After installation, the service worker waits for existing clients to close before activating. skipWaiting() bypasses this wait. The activate event is the correct place to clean up old caches:

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames =>
      Promise.all(
        cacheNames
          .filter(name => name !== CACHE_NAME)
          .map(name => caches.delete(name))
      )
    )
  );
  self.clients.claim(); // Take control of existing clients immediately
});

Fetch interception: The service worker intercepts all network requests in its scope:

self.addEventListener('fetch', event => {
  event.respondWith(/* caching strategy implementation */);
});

Caching Strategies

The caching strategy determines how the service worker responds to network requests. Different content types require different strategies.

Cache-First (Network Fallback)

Check cache first; fall back to network. Appropriate for static assets with versioned filenames (CSS, JS bundles with content hashes):

event.respondWith(
  caches.match(event.request).then(cached =>
    cached || fetch(event.request).then(response => {
      const clone = response.clone();
      caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
      return response;
    })
  ).catch(() => caches.match('/offline.html'))
);

Network-First (Cache Fallback)

Attempt network; fall back to cache on failure. Appropriate for frequently updated content like API responses and HTML documents:

event.respondWith(
  fetch(event.request)
    .then(response => {
      const clone = response.clone();
      caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
      return response;
    })
    .catch(() => caches.match(event.request))
);

Stale-While-Revalidate

Return cached response immediately; update cache from network in background. Appropriate for content where speed is prioritized over freshness (blog posts, product listings):

event.respondWith(
  caches.open(CACHE_NAME).then(cache =>
    cache.match(event.request).then(cached => {
      const networkFetch = fetch(event.request).then(response => {
        cache.put(event.request, response.clone());
        return response;
      });
      return cached || networkFetch;
    })
  )
);

Workbox (from Google) provides a production-grade caching strategy library that eliminates the need to write service worker fetch handlers manually. Workbox is strongly recommended for production PWAs — hand-rolled service workers have a high incidence of subtle bugs in cache invalidation and update handling.

Web App Manifest

The manifest is a JSON file linked from the HTML <head> that declares the PWA's identity and installation behavior:

{
  "name": "Application Full Name",
  "short_name": "AppName",
  "description": "Brief application description",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#2563eb",
  "background_color": "#ffffff",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

Key fields:

  • display: "standalone": Opens the app without browser UI (address bar, navigation buttons). This is the field that produces the native app appearance.
  • start_url: The URL opened when the user launches the installed app. Should include a query parameter for analytics attribution (?source=pwa).
  • icons: Provide both a standard icon and a maskable icon. Maskable icons are safe to crop by OS icon shapes (circles, rounded squares). Without a maskable icon, the OS uses the standard icon with white padding, which looks unprofessional.
  • theme_color: Colors the browser UI chrome (address bar, status bar) on supported platforms.

Push Notifications

PWA push notifications use the Web Push API and require user permission. Since iOS 16.4, push notifications work in installed PWAs on Apple devices — the final major platform gap has closed.

Requesting permission (handle in response to user gesture, not on page load):

async function requestNotificationPermission() {
  const permission = await Notification.requestPermission();
  if (permission === 'granted') {
    await subscribeUserToPush();
  }
}

Creating a push subscription:

async function subscribeUserToPush() {
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
  });
  // Send subscription to your backend
  await saveSubscriptionToServer(subscription);
}

Handling push events in the service worker:

self.addEventListener('push', event => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      badge: '/icons/badge-72.png',
      data: { url: data.url }
    })
  );
});

self.addEventListener('notificationclick', event => {
  event.notification.close();
  event.waitUntil(
    clients.openWindow(event.notification.data.url)
  );
});

VAPID (Voluntary Application Server Identification) keys authenticate the push server. Generate them once and store them securely. The private key never leaves the server; the public key is used in browser subscriptions.

Notification strategy: Unsolicited or irrelevant notifications drive permission revocation. Best practice: opt-in to specific notification categories, not a blanket "enable notifications." Users who choose their notification types maintain permissions significantly longer.

Installability and Add to Home Screen

The browser displays an "Add to Home Screen" prompt when:

  • A valid manifest file is linked
  • A service worker is registered
  • The page is served over HTTPS
  • The user has engaged with the page (Chrome requires engagement signals)

Triggering the prompt programmatically:

let deferredPrompt;
window.addEventListener('beforeinstallprompt', event => {
  event.preventDefault();
  deferredPrompt = event;
  showInstallButton(); // Show your custom install UI
});

document.getElementById('install-button').addEventListener('click', async () => {
  if (deferredPrompt) {
    deferredPrompt.prompt();
    const { outcome } = await deferredPrompt.userChoice;
    deferredPrompt = null;
    if (outcome === 'accepted') trackInstall();
  }
});

Trusted Web Activities (TWA): PWAs can be packaged as Android apps distributed through Google Play using TWA, which wraps the PWA in a Chrome Custom Tab. This approach accesses Play Store distribution and discovery without duplicating app logic. Bubblewrap (Google's PWA-to-TWA CLI tool) automates the packaging process.

Performance Optimization for PWAs

PWA performance directly affects Core Web Vitals, which Google uses as a ranking signal and as a measure of user experience quality.

App Shell Architecture: Cache the minimal HTML/CSS/JS skeleton that renders the application chrome immediately. Load content dynamically. Repeat visitors experience near-instant load from cache; the "shell" is always available even offline.

Preloading critical resources: Use <link rel="preload"> for fonts, hero images, and critical JavaScript. Reduce LCP by starting resource downloads before the browser discovers them through normal parsing.

Background sync: For PWAs with write operations (form submissions, data updates), use the Background Sync API to queue operations when offline and execute them when connectivity returns:

self.addEventListener('sync', event => {
  if (event.tag === 'sync-form-data') {
    event.waitUntil(syncPendingData());
  }
});

Framework Support

All major frontend frameworks provide first-class PWA tooling:

Next.js: next-pwa plugin generates service worker and manifest with minimal configuration. Integrates with Workbox for caching strategies. App Router supports granular caching policies per route.

Nuxt.js: @vite-pwa/nuxt module provides Workbox-backed service worker generation and manifest management.

Vue + Vite: vite-plugin-pwa adds PWA support to any Vite project. The most flexible option for non-Nuxt Vue applications.

Angular: @angular/service-worker provides built-in PWA support. ng add @angular/pwa generates service worker, manifest, and configuration. The Angular approach is more opinionated than Workbox but well-integrated with Angular's build pipeline.

React (CRA): The create-react-app PWA template is deprecated. Use Vite with vite-plugin-pwa for new React PWA projects.

When PWA Is and Is Not the Right Choice

PWA is appropriate when:

  • Content-first platform: news, documentation, e-commerce product listings, blogs
  • Cross-platform reach without separate native app development cost
  • SEO is a key traffic source (PWAs are fully crawlable; native apps are not)
  • Fast time to market is prioritized
  • App distribution through URL sharing matters

Native is appropriate when:

  • Hardware access requirements: Bluetooth, NFC, advanced camera control, ARKit/ARCore
  • Graphically intensive applications: 3D games, AR/VR experiences
  • Platform-specific integrations: Apple Pay, Google Pay native flows, Siri/Google Assistant
  • Compliance requirements mandating App Store distribution

The decision is not binary: A PWA providing the primary user journey supplemented by a thin native wrapper (for hardware features) optimizes cost and coverage. The native app handles the 5% of functionality requiring hardware access; the PWA handles the 95% that does not.

In projects at Smart Maple involving content platforms, internal tools, and e-commerce frontends, PWA has consistently been the better choice when SEO and cross-platform reach are requirements — native codebases require platform-specific maintenance that compounds over time for organizations without dedicated mobile engineering teams.

Testing and Debugging PWAs

PWA development introduces debugging scenarios that standard web development tooling does not handle by default.

Service worker debugging: Chrome DevTools Application panel provides service worker registration status, cache contents, and background sync queue visibility. The "Update on reload" flag forces a service worker update on every page load — essential for development to prevent stale service worker versions intercepting requests.

Offline testing: DevTools Network panel has an "Offline" checkbox that takes the page offline. The service worker's offline fallback should serve a meaningful page — not a blank white screen or browser error page. Test every offline state explicitly.

Push notification testing: DevTools Application > Service Workers provides a push notification test interface. Send a test push payload to verify the service worker's push event handler without requiring a real push subscription.

Lighthouse PWA audit: The Lighthouse PWA checklist validates installability criteria, service worker registration, HTTPS, and manifest configuration. Running Lighthouse in CI with a minimum score gate catches PWA regressions before they reach production.

Testing on real devices: Service worker behavior, push notification UI, and "Add to Home Screen" prompt appearance differ between browsers and operating systems. Test the full install and notification flow on iOS Safari (iPhone), Android Chrome, and desktop Chrome at minimum. Emulators reproduce most scenarios but not all.

Deployment Considerations

HTTPS: All production PWAs require HTTPS. Most hosting providers (Vercel, Netlify, Cloudflare Pages) provision TLS certificates automatically. For self-hosted deployments, Let's Encrypt with Certbot provides free, automated certificate management.

Service worker scope: The service worker file must be served from the root of the scope it controls. A service worker at /app/sw.js controls only /app/* URLs. For full-site PWA coverage, serve the service worker from /sw.js.

Cache versioning strategy: Version the CACHE_NAME constant with each deployment to ensure updated assets replace cached versions. Workbox handles this automatically via asset manifest integration. Manual service workers require discipline to update cache names on each release.

Performance budget enforcement: Set Lighthouse CI thresholds for Core Web Vitals in the CI pipeline. A PWA that scores below 75 on Lighthouse performance is not delivering the user experience that PWA is supposed to provide.

Related Articles

August 11, 2026

MLOps Guide: Taking Machine Learning Models to Production [2026]

87% of machine learning models built by data science teams never reach production. The models work — they pass cross-validation, they score well on holdout sets, they demonstrate genuine predictive value. The problem is not the modeling. The problem is everything that happens between a notebook experiment and a reliable, monitored, production system. MLOps is the discipline that closes that gap. This guide covers the full MLOps stack: maturity levels, tooling choices (MLflow, DVC, Kubeflow

Read More
August 10, 2026

LLM Fine-Tuning Guide: Custom Model Training with LoRA and QLoRA [2026]

General-purpose LLMs are impressive. They can write code, summarize documents, answer questions, and translate between languages with reasonable accuracy. But "reasonable" is not good enough when your application requires consistent output format, domain-specific terminology, a particular tone, or behavior that the base model was never trained to exhibit. That gap is where fine-tuning matters. Fine-tuning updates a model's weights on your specific data, changing how the model behaves — not

Read More
August 9, 2026

Computer Vision Applications: Object Detection, OCR, and Industrial AI [2026]

Computer vision has moved well past the research phase. The models are trained, the frameworks are mature, the hardware is accessible, and the use cases are generating measurable returns. What was a specialized capability requiring deep expertise in 2018 is now deployable infrastructure — if you know which component to reach for and where the real complexity lives. This guide covers computer vision applications across industrial, medical, logistics, and document processing domains. It expl

Read More