Skip to content

Progressive Web Apps — Offline, Manifest & Service Workers Complete Guide

DodaTech Updated 2026-06-22 6 min read

In this tutorial, you'll learn about Progressive Web Apps. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Progressive Web Apps (PWAs) are web applications that use modern browser capabilities to deliver native app-like experiences including offline support, push notifications, and home screen installation without app store distribution.

What You'll Learn & Why It Matters

In this tutorial you will learn how to convert any website into a PWA by adding a web app manifest, registering a service worker, implementing offline Caching strategies, and sending push notifications. PWAs are supported by Chrome, Firefox, Safari, and Samsung Internet, reaching over 6 billion devices.

Real-world use: Pinterest rebuilt their mobile site as a PWA and saw a 60 percent increase in core engagement. Doda Browser's mobile version uses service worker Caching to load the new tab page instantly even on slow networks.

Prerequisites

  • Basic JavaScript and HTML knowledge
  • Familiarity with HTTPS (PWAs require HTTPS)
  • A simple existing website or web app

Learning Path

flowchart LR
  A[App Development Overview] --> B[Progressive Web Apps]
  B --> C[Cross-Platform Tools]
  B --> D[Mobile Analytics]
  B --> E[App Monetization]
  B:::current
  classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px

The Web App Manifest

The manifest is a JSON file that tells the browser about your PWA. It controls the app name, icons, splash screen, and display mode.

{
  "name": "DodaTask",
  "short_name": "DodaTask",
  "description": "A simple task manager PWA",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#006B5E",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable]
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

Expected behavior: When the browser detects the manifest via a <link> tag, it shows an install prompt. The app opens without browser chrome in standalone mode.

Link it in your HTML <head>:

<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#006B5E" />

Service Worker Basics

A service worker is a JavaScript file that runs in the background, separate from the web page. It intercepts network requests, caches assets, and enables offline functionality.

// sw.js
const CACHE_NAME = "dodatask-v1";
const ASSETS = [
  "/",
  "/index.html",
  "/styles.css",
  "/app.js",
  "/icons/icon-192.png",
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log("Caching app shell");
      return cache.addAll(ASSETS);
    })
  );
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys
          .filter((key) => key !== CACHE_NAME)
          .map((key) => caches.delete(key))
      );
    })
  );
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      return cachedResponse || fetch(event.request);
    })
  );
});

Expected behavior: On the first visit, the service worker installs and caches all assets in ASSETS. On subsequent visits (even offline), the app loads from the cache.

Caching Strategies

Different resources need different Caching strategies. Here are the most common ones:

Cache First, Network Fallback

Use for static assets like CSS, JS, and fonts. Serve from cache instantly; fetch from network only if the cache misses.

async function cacheFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    return new Response("Offline", { status: 503 });
  }
}

Network First, Cache Fallback

Use for API calls and dynamic content. Try the network first; fall back to cache when offline.

async function networkFirst(request) {
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    const cached = await caches.match(request);
    if (cached) return cached;
    return new Response("Offline", { status: 503 });
  }
}

Expected behavior: API-driven features work while online and gracefully degrade to stale cached data when offline.

Push Notifications

PWAs can send push notifications through the Push API and the Notifications API.

// Request permission in your app
async function subscribeUser() {
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(publicVapidKey),
  });
  // Send subscription to your server
  await fetch("/api/subscribe", {
    method: "POST",
    body: JSON.stringify(subscription),
    headers: { "Content-Type": "application/json" },
  });
}

// Handle push events in the service worker
self.addEventListener("push", (event) => {
  const data = event.data.json();
  const options = {
    body: data.body,
    icon: "/icons/icon-192.png",
    badge: "/icons/badge-96.png",
    data: { url: data.url },
  };
  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});

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

Expected behavior: After the user grants permission, push messages from the server trigger a system notification. Clicking the notification opens the specified URL.

The PWA Architecture Diagram

flowchart TD
  A[Browser] --> B[Service Worker]
  B --> C[Cache Storage]
  B --> D[Network]
  B --> E[Push Server]
  A --> F[Web App Manifest]
  F --> G[Home Screen Install]
  A --> H[IndexedDB]
  C --> I[Static Assets]
  D --> J[API Responses]
  E --> K[Notifications]
  H --> L[User Data]

Common Errors & Mistakes

1. Missing HTTPS

Mistake: Trying to register a service worker on HTTP. Browsers reject service workers on insecure origins.

Fix: Serve your app over HTTPS. For local development, localhost is exempt.

2. Incorrect Cache Key Scope

Mistake: Registering the service worker from a subdirectory, limiting its scope to that directory.

Fix: Place sw.js in the root directory or use the scope option in registration.

3. Forgetting to Update the Cache Version

Mistake: Updating assets but keeping the same cache name. Old cached files are served instead of new ones.

Fix: Bump the CACHE_NAME on each deploy and clean up old caches in the activate event.

4. Not Handling the Update Flow

Mistake: Users get stuck on the old service worker until they close all tabs.

Fix: Listen for statechange on the waiting worker and call skipWaiting() with a user-facing update prompt.

5. Push Permission Denied Without Fallback

Mistake: Assuming push permission is always granted and crashing when it is denied.

Fix: Check Notification.permission and degrade gracefully, showing an in-app notice instead of notifications.

Practice Questions

Question 1

What is the difference between a service worker and a web worker?

Show answer A service worker acts as a network proxy and can intercept fetch events, handle push messages, and work offline. A web worker is for CPU-intensive background tasks and cannot access the DOM or network.

Question 2

Why does a PWA require HTTPS?

Show answer HTTPS prevents man-in-the-middle attacks that could hijack the service worker or intercept cached data. Service worker APIs are powerful and would be dangerous on insecure connections.

Question 3

What does display: standalone do in the manifest?

Show answer It tells the browser to launch the app without browser UI elements like the address bar and tab strip, giving it a native app-like appearance.

Question 4

How can you force a service worker update?

Show answer Call `registration.update()` in JavaScript or change the service worker file byte-for-byte (the browser detects any change). Bump the version number in a comment to trigger updates.

Challenge

Build a PWA that caches Wikipedia articles for offline reading. Use the Network First Strategy for live articles and Cache First for previously viewed articles. Add a "Read Later" button that stores articles in IndexedDB.

Mini Project: Offline Notes PWA

Build a note-taking PWA that works fully offline. Store notes in IndexedDB using the idb library. Sync to a remote API when the network becomes available (use the online and offline events). Add the manifest, a service worker with Cache First for static assets, and a push notification that reminds the user about unsynced notes.


Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Author: DodaTech | Last updated: June 22, 2026

DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro