Skip to content

Offline Web Apps

This lesson explains Offline Web Apps with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.

HTML5 Offline Apps Overview

HTML5 Offline Apps allow web applications to continue working when the user has no internet connection or an unstable network. Modern offline web apps usually use Service Workers, Cache API, IndexedDB, Web App Manifest, Fetch API, and Background Sync to provide an app-like experience in the browser.

Offline apps are commonly used for Progressive Web Apps, note-taking tools, dashboards, forms, e-commerce carts, document editors, learning platforms, field-service apps, travel apps, and mobile-first web applications that must work reliably in poor network conditions.

Feature Description
Offline Technology Service Workers, Cache API, IndexedDB, Background Sync
Main Goal Keep app content and features usable without internet.
Best For PWAs, dashboards, forms, notes, carts, field apps
Storage Cache API for responses, IndexedDB for structured app data
Security Requires HTTPS in production for Service Workers
Old Approach Application Cache is deprecated and should not be used.

Offline App Architecture

A modern offline app separates static assets, HTTP responses, and structured user data into the correct browser storage systems.

Layer API Purpose
App Shell Service Worker + Cache API Cache HTML, CSS, JS, icons, fonts, and core UI.
Network Requests Fetch API + Service Worker Intercept requests and choose cache or network strategy.
Structured Data IndexedDB Store records, drafts, offline changes, and sync queues.
Small Preferences Local Storage Store theme, language, layout, and simple settings.
Installable App Web App Manifest Add app name, icons, display mode, and start URL.
Retry Failed Writes Background Sync Send saved changes when the network returns.

Register a Service Worker

// main.js

if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/service-worker.js")
      .then((registration) => {
        console.log(
          "Service Worker registered:",
          registration.scope
        );
      })
      .catch((error) => {
        console.error(
          "Service Worker registration failed:",
          error
        );
      });
  });
}

A Service Worker runs separately from the main page and can intercept network requests, cache responses, and serve cached content when the user is offline.

Cache App Shell During Install

// service-worker.js

const CACHE_NAME =
  "offline-app-v1";

const APP_SHELL =
  [
    "/",
    "/offline",
    "/styles/main.css",
    "/scripts/app.js",
    "/icons/icon-192.png"
  ];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => {
        return cache.addAll(APP_SHELL);
      })
  );
});

The install event is commonly used to cache the core files needed to open and display the application even without a network connection.

Cache-First Strategy Example

// service-worker.js

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

        return fetch(event.request);
      })
  );
});

A cache-first strategy is useful for static assets such as CSS, JavaScript, images, fonts, and app shell files.

Network-First Strategy Example

// service-worker.js

self.addEventListener("fetch", (event) => {
  event.respondWith(
    fetch(event.request)
      .then((networkResponse) => {
        const responseClone =
          networkResponse.clone();

        caches.open(CACHE_NAME)
          .then((cache) => {
            cache.put(
              event.request,
              responseClone
            );
          });

        return networkResponse;
      })
      .catch(() => {
        return caches.match(event.request);
      })
  );
});

A network-first strategy is useful for content that should be fresh when online but still available from cache when offline.

Offline Fallback Page

// service-worker.js

self.addEventListener("fetch", (event) => {
  if (event.request.mode === "navigate") {
    event.respondWith(
      fetch(event.request)
        .catch(() => {
          return caches.match("/offline");
        })
    );
  }
});

Offline fallback pages help users understand that the app is offline instead of showing a browser error page.

Cache API Methods

Method Description Example Syntax
caches.open() Opens or creates a named cache. caches.open("offline-v1")
cache.addAll() Adds multiple files to cache. cache.addAll(files)
cache.put() Stores a request and response pair. cache.put(request, response)
caches.match() Finds a matching cached response. caches.match(request)
caches.keys() Lists cache names. caches.keys()
caches.delete() Deletes a named cache. caches.delete("old-cache")

Save Offline Form Draft with IndexedDB

// Save draft data for later sync

function saveDraft(database, draft) {
  const transaction =
    database.transaction("drafts", "readwrite");

  const store =
    transaction.objectStore("drafts");

  store.put(draft);
}

saveDraft(
  database,
  {
    id: "contact-form",
    name: "John",
    message: "Need more details",
    synced: false
  }
);

IndexedDB is better than Local Storage for offline app data because it can store structured records, larger data, files, blobs, and sync queues.

Background Sync Example

// main.js

async function registerSync() {
  const registration =
    await navigator.serviceWorker.ready;

  if ("sync" in registration) {
    await registration.sync.register(
      "sync-drafts"
    );
  }
}
// service-worker.js

self.addEventListener("sync", (event) => {
  if (event.tag === "sync-drafts") {
    event.waitUntil(
      syncDrafts()
    );
  }
});

Background Sync allows a Service Worker to retry failed network actions when the connection returns. Browser support may vary, so always provide a manual retry option.

Detect Online and Offline Status

window.addEventListener("online", () => {
  document.body.classList.remove("is-offline");
  console.log("Back online.");
});

window.addEventListener("offline", () => {
  document.body.classList.add("is-offline");
  console.log("You are offline.");
});

Online and offline events help update the UI, but they do not guarantee that a specific server is reachable. Always handle failed Fetch requests too.

Web App Manifest Example

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

A Web App Manifest helps make offline web apps installable and app-like on supported devices.

Common Offline Caching Strategies

Strategy How It Works Best Use
Cache First Return cache if available, otherwise use network. CSS, JS, images, fonts, app shell.
Network First Try network first, fallback to cache. News, dashboards, dynamic content.
Stale While Revalidate Return cached data quickly and update cache in background. Frequently visited pages and API data.
Network Only Always request from network. Payments, authentication, sensitive operations.
Cache Only Only use pre-cached files. Versioned static offline assets.

Common Offline App Use Cases

  • Note-taking applications that sync later.
  • Field-service apps used in weak network areas.
  • E-commerce carts that survive connection loss.
  • Learning apps with cached lessons and quizzes.
  • Travel apps with saved itineraries and maps.
  • Dashboards that show cached reports offline.
  • Forms that save drafts and submit when online.
  • Media apps that cache selected content for later access.

Offline Apps Best Practices

  • Use Service Workers and Cache API instead of deprecated Application Cache.
  • Cache the app shell separately from dynamic data.
  • Use IndexedDB for structured offline records and sync queues.
  • Show clear online, offline, syncing, and failed states.
  • Provide manual retry options when background sync is unavailable.
  • Version caches and delete old caches during activation.
  • Do not cache sensitive data unless absolutely necessary.
  • Test offline behavior using browser DevTools and real devices.

Common Offline App Mistakes

  • Using deprecated Application Cache for new apps.
  • Caching everything without a clear strategy.
  • Not updating or deleting old caches.
  • Assuming navigator.onLine guarantees server availability.
  • Not handling data sync conflicts.
  • Storing sensitive user data without encryption or clear need.
  • Breaking deep links or refresh behavior in offline mode.
  • Not communicating offline and sync status to users.

Offline-First vs Online-First Apps

Feature Offline-First Online-First
Main Assumption Network may be unavailable. Network is usually available.
Data Storage Local database first, sync later. Server first, cache optional.
User Experience App continues working offline. App may show error or loading state offline.
Complexity Higher due to sync and conflicts. Lower for simple apps.
Best Use Mobile, field, travel, notes, forms. Banking, payments, real-time server data.

Key Takeaways

  • Modern offline apps use Service Workers, Cache API, IndexedDB, and Web App Manifest.
  • Service Workers intercept network requests and serve cached content.
  • Cache API stores HTTP responses and app shell files.
  • IndexedDB stores structured offline data and pending changes.
  • Background Sync can retry failed requests when the network returns.
  • Offline apps need clear caching, syncing, conflict, and fallback strategies.

Pro Tip

Start offline support with the app shell first: cache the main page, CSS, JavaScript, icons, and offline fallback page. Then add IndexedDB for drafts and sync queues only when your app needs true offline data editing.