VOL. IV • ISSUE 38 Independent Research & Engineering Dispatch
Editorial Charter •
DEVV.IN
The Software Architecture, Applied Intelligence & Scientific Dispatch
Durgesh Sharma (Dev) Chief Editor & Software Architect
PEER-REVIEWED
STATUS: VERIFIED

Building Progressive Web Apps (PWA): Offline-First Service Workers & Caching

Building Progressive Web Apps (PWA): Offline-First Service Workers & Caching

In an increasingly mobile-first, bandwidth-constrained global economy, native mobile applications distributed through centralized app stores have traditionally held a monopoly over offline usability, background push notifications, and hardware integration. Progressive Web Apps (PWAs) have completely shattered this monopoly. Built on standards-compliant modern web APIs, a well-engineered PWA delivers offline resilience, 60fps interaction responsiveness, home screen installability, and background background synchronization—all from a single, cross-platform web codebase that bypasses app store approval gatekeepers and platform distribution taxes.

The Core Pillar: Service Worker Lifecycle Architecture

At the center of every resilient PWA is the Service Worker. A Service Worker is a specialized event-driven background Web Worker that the browser runs on a completely independent execution thread, decoupled from the active web page DOM. It functions as an in-browser programmable network proxy, intercepting every outbound HTTP request emitted by your application.

The Service Worker lifecycle consists of three distinct phases:

  1. Registration: The client page registers the worker script: navigator.serviceWorker.register('/sw.js').
  2. Installation (install event): The worker initializes and pre-caches the critical Application Shell (HTML shell, CSS styles, icons, offline fallback pages). If any critical asset fails to download, installation aborts, preventing broken caches.
  3. Activation (activate event): The worker purges outdated cache versions from previous deployments, updates database schemas, and claims control of all open client tabs using clients.claim().
Critical Lifecycle Rule: Service Workers do not automatically take control of currently open pages upon first load unless explicitly commanded via self.skipWaiting() and clients.claim(). Always architect your activation logic to ensure seamless cache transitions without breaking active user sessions.
// Production-Grade Service Worker: Cache-First with Stale-While-Revalidate (sw.js)
const CACHE_VERSION = 'devvin-v4-release';
const STATIC_APP_SHELL = [
  '/',
  '/index.html',
  '/about.html',
  '/css/main.css',
  '/css/article.css',
  '/offline.html',
  '/favicon.ico'
];

// 1. Install Phase: Cache Static Application Shell
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_VERSION).then((cache) => {
      console.log('[SW] Pre-caching application shell...');
      return cache.addAll(STATIC_APP_SHELL);
    }).then(() => self.skipWaiting())
  );
});

// 2. Activate Phase: Purge Stale Historical Caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys.map((key) => {
          if (key !== CACHE_VERSION) {
            console.log('[SW] Purging deprecated cache bucket:', key);
            return caches.delete(key);
          }
        })
      );
    }).then(() => self.clients.claim())
  );
});

// 3. Fetch Phase: Intelligent Request Routing
self.addEventListener('fetch', (event) => {
  const request = event.request;
  const url = new URL(request.url);

  // Ignore non-GET requests (Server Actions, mutations)
  if (request.method !== 'GET') return;

  // Strategy A: Stale-While-Revalidate for Static Assets (CSS, JS, Images)
  if (url.pathname.match(/\.(css|js|woff2|png|svg|jpg)$/)) {
    event.respondWith(
      caches.open(CACHE_VERSION).then(async (cache) => {
        const cachedResponse = await cache.match(request);
        const fetchPromise = fetch(request).then((networkResponse) => {
          if (networkResponse.status === 200) {
            cache.put(request, networkResponse.clone());
          }
          return networkResponse;
        }).catch(() => cachedResponse);

        return cachedResponse || fetchPromise;
      })
    );
    return;
  }

  // Strategy B: Network-First with Offline Fallback for HTML Documents
  event.respondWith(
    fetch(request).catch(async () => {
      const cache = await caches.open(CACHE_VERSION);
      const cachedPage = await cache.match(request);
      return cachedPage || cache.match('/offline.html');
    })
  );
});

Offline Storage Hierarchy: CacheStorage vs. IndexedDB

Modern progressive web applications require two fundamentally distinct client-side storage tiers:

Storage System Data Type Supported Capacity Limit Access Paradigm Primary Production Use Case
CacheStorage API Request / Response HTTP Pairs Multiple Gigabytes (Quota-managed) Asynchronous (Promised-based) Static HTML, CSS stylesheets, bundled JS, web fonts, media assets.
IndexedDB Structured JavaScript Objects & Blobs Up to 80% of free disk space Asynchronous (Transactional) Offline drafts, user shopping carts, sync queues, client relational state.
localStorage (Deprecated) Strictly Synchronous Strings ~5 MB Synchronous (Blocks Event Loop!) Never use in high-performance PWAs; synchronous reads block UI frames.

Web App Manifest & Installability Criteria

To trigger the native browser install prompt ("Add to Home Screen" or the desktop install button in Chrome/Edge), your application must serve a valid manifest.webmanifest linked with the correct MIME type (application/manifest+json):

{
  "name": "Devv.in — Engineering & Architecture Dispatch",
  "short_name": "Devv.in",
  "description": "The Software Architecture, Applied Intelligence & Scientific Dispatch.",
  "start_url": "/?source=pwa",
  "display": "standalone",
  "background_color": "#0d1117",
  "theme_color": "#090d16",
  "orientation": "any",
  "icons": [
    {
      "src": "/assets/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/assets/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

Production Checklist for Progressive Web Apps

  • HTTPS Mandate: Service Workers require an authenticated, cryptographically secure origin (https://) to prevent Man-in-the-Middle network tampering. (The only exception is http://localhost for local development).
  • Cache Invalidation Protocol: Never set aggressive HTTP Cache-Control max-age headers on the sw.js file itself. Configure your web server (Nginx/Cloudflare) to serve sw.js with Cache-Control: no-cache, no-store, must-revalidate so the browser checks for updates on every page visit.
  • Background Sync Resilience: Use the BackgroundSync API to register mutations made while offline. When network connectivity restores, the browser automatically wakes the Service Worker to replay pending POST requests even if the user has navigated away from your website.

How to Cite This Technical Dispatch

Sharma, D. (2026). "Building Progressive Web Apps (PWA): Offline-First Service Workers & Caching". Devv.in Research Dispatch, Vol. IV, Issue 38. https://devv.in/posts/progressive-web-apps-offline-first-architecture.html

← PREVIOUS DISPATCH Modern CSS Superpowers: Subgrid, Container Queries & Modern Pseudo-Classes NEXT DISPATCH → REST vs GraphQL vs gRPC: Architectural Guide to Modern API Design