How to Achieve Long-Term Offline Caching with HTTP, AppCache, and Service Workers

This article explains why server‑generated URLs with authentication break traditional HTTP caching and then guides readers through three offline‑caching strategies—HTTP strong and negotiated caching, the now‑deprecated Application Cache, and modern Service Workers—providing practical code examples and best‑practice recommendations.

ELab Team
ELab Team
ELab Team
How to Achieve Long-Term Offline Caching with HTTP, AppCache, and Service Workers

Background

When static resource URLs are generated by the server with authentication and expiration parameters, the usual HTTP caching mechanism becomes ineffective: once the URL expires the resource is fetched again even though the content has not changed. This article explores offline/long‑term caching from three perspectives: HTTP caching, Application Cache, and Service Worker.

HTTP Caching

HTTP caching stores fetched resources locally so that subsequent requests can reuse them, reducing network traffic and improving performance. The cache process consists of strong cache validation, negotiated cache validation, and finally a network request. Only GET requests are cacheable.

Cache Flow

Browser request → strong cache validation → negotiation cache validation → network request.

Strong Cache

Chrome maintains two strong caches: disk cache and memory cache. Requests served from these caches appear in DevTools as status 200 with “from disk cache” or “from memory cache”. Strong cache is controlled by three HTTP header fields: Expires , Pragma , and Cache‑Control .

Expires

Expires is an HTTP/1.0 header with the lowest priority. If the current time is before the timestamp the cache is fresh; otherwise it is stale and negotiation cache is used. An invalid date (e.g., 0) forces immediate expiration.

Cache‑Control

Cache‑Control (HTTP/1.1) supports directives such as no-store, no-cache, private, public, max-age, and must-revalidate.

Pragma

Pragma is an HTTP/1.0 header with only the no-cache value; when both Pragma and Cache‑Control are present, Pragma takes precedence.

Negotiated Cache

If strong cache is bypassed, the browser uses Last‑Modified/If‑Modified‑Since and ETag/If‑None‑Match . A 304 response means the cached copy is still valid; otherwise a fresh 200 response is stored.

Last‑Modified/If‑Modified‑Since

The server sends the resource’s last modification time in Last‑Modified; the client returns it in If‑Modified‑Since to check freshness.

ETag/If‑None‑Match

ETag is a hash identifier. Strong validators compare exact hashes; weak validators (prefixed with W/) allow minor changes.

Differences

ETag has higher priority than Last‑Modified.

Last‑Modified suffers from a 1‑second granularity issue.

Proxy Cache (Vary)

The Vary header lists request‑header fields that affect the cached representation, enabling proxies to store different versions of a resource.

Application Cache (Deprecated)

Although still supported by some browsers, HTML5 Application Cache is deprecated in favor of Service Workers.

Overview

Application Cache uses a manifest file to list resources that should be cached for offline use. It is suitable for applications with infrequent content changes.

Manifest Structure

CACHE MANIFEST
# version 1.0

CACHE:
 /static/img/example.png
 http://localhost:8080/static/img/setting-icon.png

NETWORK:
 *

FALLBACK:
 /html5/ /404.html

Configuration Sections

CACHE : files to cache.

NETWORK : resources that must be fetched from the network.

FALLBACK : fallback resources when the original cannot be retrieved.

Usage

Add the manifest="manifest.appcache" attribute to the <html> tag and serve the file with MIME type text/cache-manifest.

Accessing the Cache

The window.applicationCache object exposes status values (UNCACHED, IDLE, CHECKING, DOWNLOADING, UPDATEREADY, OBSOLETE) and events (cached, checking, downloading, error, noupdate, obsolete, progress, updateready) that can be used to monitor and control the cache.

Service Worker

Overview

Service Workers are background scripts (a type of Web Worker) that run on a separate thread, can intercept network requests, and provide persistent offline caching via the Cache Storage API.

Features

Runs off the main JavaScript thread, does not block page rendering.

Installed once and persists until manually unregistered.

Can intercept fetch events and serve cached responses.

Cannot access the DOM directly.

Requires HTTPS in production (localhost allowed for development).

Uses promises extensively; cache size is limited by the browser’s quota manager.

Scope

The scope is the URL path that the Service Worker controls. It defaults to the registration path but can be overridden with the scope option.

Lifecycle

The worker progresses through installing → installed → activating → activated → redundant . The install event is typically used to pre‑cache resources, while the activate event can clean up old caches.

In the install handler call event.waitUntil() to delay completion until caching finishes. Optionally call self.skipWaiting() to activate immediately.

Workflow

Register the Service Worker from the main thread.

Browser downloads and parses the script, triggering the install event.

If install succeeds, the activate event fires; otherwise the worker is terminated.

After activation the worker can intercept fetch requests within its scope.

Unregistering or installing a newer version moves the old worker to the redundant state.

Example Registration

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('service-worker.js')
      .then(registration => {
        // registration successful
      })
      .catch(err => {
        // registration failed
      });
  });
}

Example Service Worker

const CACHE_VERSION = 'unique_v1';

self.addEventListener('activate', event => {
  const cachePromise = caches.keys().then(keys => {
    return Promise.all(
      keys.map(key => {
        if (key !== CACHE_VERSION) {
          return caches.delete(key);
        }
      })
    );
  });
  event.waitUntil(cachePromise).then(() => self.clients.claim());
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request, { ignoreSearch: DEFAULT_CONFIG.ignoreURLParametersMatching })
      .then(response => {
        if (response) {
          return response;
        }
        const request = event.request.clone();
        const url = request.url;
        if (matchOne(url, DEFAULT_CONFIG.exclude)) {
          return fetch(request);
        } else if (request.method === 'GET' && matchOne(url, DEFAULT_CONFIG.include)) {
          return fetch(request).then(httpRes => {
            if (httpRes && [200, 304].includes(httpRes.status)) {
              const responseClone = httpRes.clone();
              caches.open(DEFAULT_CONFIG.cacheId).then(cache => {
                cache.put(event.request, responseClone);
              });
            }
            return httpRes;
          });
        } else {
          return fetch(request);
        }
      })
  );
});

Summary

HTTP caching works per‑resource and can be used offline when strong cache is valid; Application Cache caches an entire app but is deprecated; Service Workers provide flexible per‑resource offline caching without requiring network access.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

OfflineService WorkerHTTP CachingApplication Cache
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.