Skip to content
C Codeloom
Web

Progressive Web Apps: An Introduction

Learn what makes an app a PWA: service workers, manifests, install prompts, and offline-first strategies that turn a website into something that feels native.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What turns a site into a PWA
  • Service worker lifecycle
  • Caching strategies for offline
  • Web App Manifest essentials
  • When a PWA is the right choice

Prerequisites

  • Familiar with HTTP

What and Why

A Progressive Web App is a website that uses modern browser APIs to behave like an installed application: it can be added to the home screen, launch full-screen, work offline, receive push notifications, and update without an app store.

PWAs are not a framework. They are a set of capabilities you opt into. Done well, they reduce friction (no install, no store review) while feeling close to native. Done poorly, they are slow websites with a manifest.

Mental Model

Three pillars make a PWA:

  1. A served-over-HTTPS site with reliable performance.
  2. A Web App Manifest describing how it installs.
  3. A service worker that intercepts network requests for caching and offline.

The service worker is the engine. It runs in its own thread, has no DOM, and persists across page loads. Once registered, it sits between your page and the network.

+-----------+   fetch    +-----------------+   fetch   +---------+
|  Page JS  | ---------> | Service Worker  | --------> | Network |
+-----------+            +-----------------+           +---------+
     ^                        |
     |                        v
     |                +-----------------+
     +----------------|  Cache Storage  |
                      +-----------------+
 Cache hit returns instantly. Cache miss falls through to network.
Service worker intercepts requests

This intercept lets you design offline behavior, instant page loads, and background sync.

Hands-on Example

A minimal manifest.webmanifest:

{
  "name": "Notes",
  "short_name": "Notes",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0b0b0b",
  "theme_color": "#0b0b0b",
  "icons": [
    { "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

Link it from your HTML head:

<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0b0b0b" />

Register a service worker:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

A simple cache-first strategy in sw.js:

const CACHE = 'app-v3';
const ASSETS = ['/', '/styles.css', '/app.js', '/offline.html'];

self.addEventListener('install', e => {
  e.waitUntil(caches.open(CACHE).then(c => c.addAll(ASSETS)));
});

self.addEventListener('activate', e => {
  e.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
    )
  );
});

self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(hit =>
      hit || fetch(e.request).catch(() => caches.match('/offline.html'))
    )
  );
});

Pick a strategy per resource:

  • Static assets: cache-first.
  • API GETs: stale-while-revalidate.
  • User-mutating POSTs: network-only, queued with Background Sync if offline.
  • HTML shell: network-first with a cached fallback.

Common Pitfalls

Cache-first on everything. Your users see stale UI for weeks. Version your cache key and invalidate aggressively.

Forgetting skipWaiting and clients.claim. New service workers wait until all tabs close. During development this causes endless confusion.

Shipping a service worker without a kill switch. A buggy SW can brick your site for returning users. Always have a no-op SW you can deploy to clear caches and unregister.

Treating PWAs as iOS-equivalent to native. Safari supports PWAs but with quirks: no push notifications on iOS for a long time, storage eviction, no background sync. Test on iOS specifically.

Ignoring the install prompt UX. The beforeinstallprompt event needs to be captured and shown contextually, not on first paint.

Caching cross-origin opaque responses. They take full storage quota each. Audit what you cache.

Practical Tips

  • Use Workbox unless you have a strong reason not to. It handles the boring parts (versioning, routing, strategies) safely.
  • Pre-cache only the app shell. Lazy-cache the rest as the user visits routes.
  • Audit with Lighthouse. It catches missing manifest fields, icons, and HTTPS issues.
  • Show a clear “back online” or “offline mode” indicator. Silent failures feel like bugs.
  • Use Cache-Control headers correctly even with a service worker; they still drive the network fetch path.
  • Keep your service worker file small and cache it with a short max-age. It is your update mechanism; you do not want it stuck.
  • Test updates by toggling “Update on reload” in DevTools.

Wrap-up

A PWA is a deployment strategy plus a few APIs, not a rewrite. Add a manifest, register a service worker, and pick caching strategies that match your data freshness needs.

Start small: get the install prompt working and a basic offline page. Grow from there. The first time a user opens your app on the subway with no connection and it just works, the value clicks.