Modern Web • CDN
Modern Web

CDN and Service Workers: Offline-First Caching with Edge Fallback

Priya Sharma — Content Delivery Specialist September 5, 2026 9 min read

Modern applications must load instantly on fast networks and remain usable when connectivity fails. Combining a global CDN with client-side Service Workers creates a resilient, layered cache that delivers both.

Users expect pages to load in milliseconds and stay functional when the network drops. Achieving both requires a layered strategy that pairs the global reach of a Content Delivery Network with the programmatic control of Service Workers. While a CDN reduces latency by serving assets from edge Points of Presence (PoPs) near the user, a Service Worker acts as a client-side proxy in the browser, enabling instant cache responses, background sync, and true offline support.

When integrated correctly, these technologies form a resilient pipeline: the CDN handles scalable distribution, origin shielding, and TLS termination, while the Service Worker serves cached responses instantly and gracefully falls back to edge-cached content when the network is degraded. This guide covers the architecture, cache strategies, and production patterns for building offline-first applications with CDN edge fallback.

How Service Workers and CDNs Interact

The two caches are often seen as competing, but they operate at different layers and complement each other. The CDN sits between origin and browser, caching responses at distributed edges. The Service Worker sits between browser and network, intercepting fetch events before they leave the device.

Request flow with both layers enabled:

  1. User navigation or app request is issued.
  2. Service Worker fetch handler intercepts the request.
  3. If a matching Cache Storage entry exists and the strategy allows, the response is returned instantly with no network use.
  4. Otherwise, the Service Worker forwards the request to the network.
  5. The request hits the nearest CDN edge. On a cache HIT, the edge returns immediately. On a MISS or STALE, the edge fetches from origin or shield PoP.
  6. The response flows back through the Service Worker, which may cache it for offline use, then to the page.

A critical nuance: Service Workers only control pages they scope and only over HTTPS. This aligns with modern CDN deployments that enforce TLS at the edge. The Service Worker script itself (/sw.js) must not be cached with a long TTL at the edge. Serve it with Cache-Control: no-cache or max-age=0, must-revalidate, while still benefiting from edge HTTP/2 and HTTP/3 delivery. In contrast, versioned static assets — hashed JavaScript, CSS, images, and fonts — should be cached aggressively at both layers with max-age=31536000, immutable. This dual approach maximizes origin offload and offline resilience. For a deeper view of PoPs, shielding, and request routing, see our CDN architecture overview.

Offline-First Architecture Patterns

Offline-first does not mean offline-only. It means the network is treated as an enhancement, not a requirement. Three patterns dominate production implementations:

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

1. App Shell Model

The minimal HTML, CSS, and JavaScript needed to render the skeleton is precached during the Service Worker install event. Subsequent loads render instantly from Cache Storage while dynamic content is fetched from the network or edge. Ideal for Single Page Applications and Progressive Web Apps. The CDN edge-caches these immutable shell assets for fast first install, ensuring the initial Service Worker download is rapid globally.

2. Stale-While-Revalidate for Content

For articles, product listings, or dashboards where freshness matters but instant display is prioritized, serve stale cached content immediately while fetching an update in the background. Users see content without waiting, and the cache is updated for the next visit. The CDN complements this with stale-while-revalidate and stale-if-error directives at the edge, allowing PoPs to serve stale content if the origin is unreachable.

3. Cache-First with Network Fallback for Assets

For versioned static assets, check Cache Storage first. On miss, go to network (which hits the CDN), then cache the response. This maximizes hit ratio and minimizes bandwidth. Pair with long edge TTLs for optimal offload.

Offline Fallback Requirement

Always precache a fallback for navigation requests. When both Service Worker cache and network fail, serve offline.html or cached fallback JSON. Handle navigation requests with a network-first strategy that falls back to cache, and treat API failures with a cached response or IndexedDB data. For dynamic fallback generation at the edge, explore patterns in our CDN edge computing guide.

Choosing the Right Cache Strategy

No single strategy fits all resources. The choice depends on freshness requirements, offline needs, and how the CDN should behave. Use the matrix below to map resource types to the optimal approach.

Strategy Best For Offline Support CDN Interaction
Cache First, Network Fallback Versioned assets (hashed JS/CSS, images, fonts) Excellent — serves instantly offline Long edge TTL (1 year, immutable). High HIT ratio.
Network First, Cache Fallback Navigations, HTML, authenticated API calls Good — fallback when offline Short edge TTL, stale-if-error for resilience
Stale-While-Revalidate Articles, product data, config JSON Excellent — instant stale + background update Edge stale-while-revalidate=60 mirrors behavior
Cache Only Pre-cached app shell, offline page Complete — never goes to network Not involved after precache
Network Only Non-GET, payments, real-time data None — fails when offline Pass-through, private, no-store

Cache-first is fastest but risks staleness without strict versioning. Network-first guarantees freshness but is slower and requires a fallback to work offline. Stale-while-revalidate offers the best compromise for frequently updated, non-critical content. For financial or personalized data, never use cache-first without revalidation.

Set headers at the origin to coordinate both layers. Use CDN-Cache-Control or Surrogate-Control where supported to differentiate edge TTL from browser TTL — for example, a 1-day edge cache with a 5-minute browser cache, while the Service Worker extends offline retention. Over-caching in the Service Worker can bloat client storage; implement versioned cache names and cleanup in the activate event with LRU eviction for image caches.

Edge Fallback: Resilience When Origin Fails

Edge fallback ensures that when the origin is down or the user has intermittent connectivity, the CDN can still serve a useful response that the Service Worker can cache for prolonged offline use.

Key capabilities to configure:

  • Stale Serving: Configure stale-while-revalidate=60 and stale-if-error=86400. This lets edges serve stale content during revalidation or origin errors, preventing outages from reaching users.
  • Edge Workers for Synthetic Responses: Use edge functions to generate fallback JSON or HTML from edge KV storage when both cache and origin miss — for example, a minimal product catalog.
  • Background Sync: Queue mutations (POST/PUT) in IndexedDB when offline and replay via the Background Sync API when connectivity returns. The edge should handle idempotency keys to avoid duplicates.
  • Freshness Signaling: Have the edge add X-Cache: STALE or X-Edge-Fallback: 1 headers so the Service Worker can show a “last updated” indicator.

The resulting resilience hierarchy is: Service Worker Cache (instant, offline) → CDN Edge Cache (fast, origin-independent) → Origin (authoritative). This reduces Time to First Byte and improves Largest Contentful Paint even under packet loss.

Layered Fallback: Service Worker to CDN Edge to Origin Flow showing browser request hitting Service Worker cache first, then CDN edge cache, then origin, with fallback arrows. Browser / App fetch() request Service Worker Cache Storage CDN Edge (PoP) Edge Cache + Worker Origin Authoritative EDGE FALLBACK (stale / synthetic) OFFLINE FALLBACK (Cache Storage) HIT MISS Resilience hierarchy: 1. Service Worker Cache (instant, offline) → 2. CDN Edge Cache (fast, origin-independent) → 3. Origin (authoritative)
Figure 1 — Layered fallback hierarchy. The Service Worker serves instantly offline; the CDN edge serves stale or synthetic responses when origin is unreachable.

Implementation: Production Code Patterns

Register the Service Worker with update handling, and implement a router that applies the correct strategy per request type. Keep caches versioned and never cache sensitive requests without explicit allowlisting.

// register-sw.js — register with update prompt
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', { scope: '/' }).then(reg => {
    reg.addEventListener('updatefound', () => {
      const sw = reg.installing;
      sw.addEventListener('statechange', () => {
        if (sw.state === 'installed' && navigator.serviceWorker.controller) {
          // Prompt user: new version available
          showUpdateToast();
        }
      });
    });
  });
}
// sw.js — strategy router
const CACHE_SHELL = 'app-shell-v3';
const CACHE_ASSETS = 'assets-v3';
const CACHE_CONTENT = 'content-v3';
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', e => {
  e.waitUntil(
    caches.open(CACHE_SHELL).then(c => c.addAll([
      '/', OFFLINE_URL, '/styles/app.css', '/scripts/app.js'
    ])).then(() => self.skipWaiting())
  );
});

self.addEventListener('activate', e => {
  e.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.filter(k => ![CACHE_SHELL, CACHE_ASSETS, CACHE_CONTENT].includes(k))
          .map(k => caches.delete(k))
    )).then(() => self.clients.claim())
  );
});

self.addEventListener('fetch', e => {
  const req = e.request;
  if (req.method !== 'GET') return;

  // Navigations: network-first → cache → offline page
  if (req.mode === 'navigate') {
    e.respondWith(
      fetch(req).then(r => {
        const copy = r.clone();
        caches.open(CACHE_CONTENT).then(c => c.put(req, copy));
        return r;
      }).catch(() => caches.match(req).then(m => m || caches.match(OFFLINE_URL)))
    );
    return;
  }

  // Hashed assets: cache-first
  if (req.url.match(/\.(?:js|css|woff2|png|webp)$/)) {
    e.respondWith(
      caches.match(req).then(hit => hit || fetch(req).then(res => {
        if (res.ok) {
          const clone = res.clone();
          caches.open(CACHE_ASSETS).then(c => c.put(req, clone));
        }
        return res;
      }))
    );
    return;
  }

  // API / content: stale-while-revalidate
  e.respondWith(
    caches.open(CACHE_CONTENT).then(async cache => {
      const cached = await cache.match(req);
      const network = fetch(req).then(res => {
        if (res.ok) cache.put(req, res.clone());
        return res;
      }).catch(() => cached);
      return cached || network;
    })
  );
});

Limit Cache Storage to 50–100 MB and implement LRU eviction for image caches. Precache only critical assets during install; runtime-cache the rest. For TLS, header, and scope configuration that keeps this secure, see our CDN SSL/TLS configuration guide.

Performance and Invalidation

With two cache layers, invalidation must be coordinated. The most reliable method is versioned URLs. Use content hashing (app.a3f1c2.js) so new deploys create new URLs; both CDN and Service Worker fetch new assets automatically without purging.

For unversioned HTML or APIs, use CDN surrogate keys or tag-based purging. Purge by URL or tag on deploy via the CDN API, and trigger a Service Worker update by changing sw.js bytes. Keep sw.js uncached at the edge, but byte-identical until you want an update. Use skipWaiting and clients.claim carefully — prompt users to refresh for major updates to avoid breaking in-flight sessions.

Monitor hit ratios separately: Service Worker hit rate via analytics beacons, CDN edge hit rate via X-Cache headers, and origin offload. Aim for >90% edge HIT for static assets and >60% Service Worker HIT for repeat visits. For detailed purge workflows and surrogate-key design, refer to our CDN cache invalidation strategies.

Security Considerations

Service Workers are powerful and require strict safeguards. Serve only over HTTPS, set Service-Worker-Allowed scope correctly, and enforce a Content Security Policy that restricts worker sources. Never cache sensitive or personalized responses in shared caches — use Cache-Control: private, no-store for authenticated APIs and ensure the CDN respects private directives. Validate that edge fallback does not leak user data via stale cache, and scope caches by user where needed.

Frequently Asked Questions

Should the Service Worker script itself be cached by the CDN?

No. Serve sw.js with Cache-Control: no-cache or max-age=0, must-revalidate at the edge. The browser checks for updates on every navigation, and a long edge TTL would block updates. You still benefit from edge TLS termination and HTTP/2/3 delivery.

How do Service Workers and CDN cache headers interact?

They are complementary. Cache-Control governs browser and Service Worker fetch behavior, while CDN-Cache-Control or Surrogate-Control sets a longer edge TTL where supported. The Service Worker Cache Storage is programmatic and independent, but you decide when to respect or ignore headers when caching.

What is the best cache strategy for offline-first apps?

Use cache-first for versioned static assets, stale-while-revalidate for content where instant display matters, and network-first with offline fallback for navigations and APIs. This balances speed, freshness, and offline resilience across resource types.

How do you handle cache invalidation with two layers?

Prefer content-hashed URLs to avoid purging. For HTML and APIs, use CDN surrogate-key purging on deploy and versioned Service Worker caches cleaned in the activate event. Keep sw.js uncached to ensure prompt updates.

Bottom line: Treat the Service Worker as your instant, offline-capable cache and the CDN edge as your resilient, origin-independent fallback. Version your assets, keep sw.js fresh, configure stale serving at the edge, and choose strategies per resource type. The result is a fast, reliable experience that works with or without a network.

References & Standards: OWASP CDN Security · W3C Web Performance