SwoffSwoff

Hybrid SW + Client Model

Why Swoff splits responsibilities between service worker and client scopes.

Why a service worker + client hybrid?

Swoff splits responsibilities between two scopes:

Service worker (SW) scope — runs in a separate thread, survives page navigation, has access to Cache Storage API:

  • Intercepts fetch requests and applies caching strategies
  • Manages precached assets at install time
  • Maintains persistent SSE/WebSocket connections for server push
  • Handles push notification events
  • Broadcasts invalidation across clients (cross-tab sync)
  • Serves cached responses when offline
  • Intelligently dispatches fallbacks: navigation requests get an HTML fallback chain (precache → runtime-html → per-route → global → 503), while subresources (JS, CSS, images, API) return a clean 502 — preventing the old behavior of serving HTML for non-HTML requests

SW generation

Swoff splits SW generation into two phases. The CLI reads swoff.config.json and produces supporting files in swoff/. The SW-specific template has config features baked in (strategies, auth, tags, push handlers) but still has two placeholders for build-time values: ASSETS_TO_CACHE, AUTO_SKIP_WAITING. A build step (run after your framework build) scans built assets, resolves the cache name, and writes the final SW to the output directory.

Versioning

SW updates are content-based. Every build produces a byte-different sw.js — if your built assets changed, the SW content changes, and the browser detects the update automatically. No version string to manage. Cache names are fixed: "precache", "swoff-runtime", "swoff-runtime-html". Additionally, every build appends a timestamp constant at the end of the SW. This constant is purely a byte-differ — it triggers browser update detection when the SW file changes. On activation, the SW compares this timestamp against an IndexedDB record (swoff-meta DB, cacheName key). On deploy mismatch, all three caches are deleted before the new SW takes over.

Client (window) scope — runs in the page, has access to DOM, IndexedDB, and framework adapters:

  • Tracks mutation state per-operation
  • Queues offline writes to IndexedDB (the SW could also write, but client-side queuing keeps mutation state accessible to UI components and avoids SW lifecycle complexity)
  • Provides reactive hooks (useSwoffFetch, useSwoffQueue)
  • Manages auth tokens in memory-only storage (never exposed to SW)
  • Detects online/offline state and triggers mutation replay

The hybrid model exists because:

  • Stale detection requires the SW to know about cached responses (SW scope)
  • Background refresh requires the SW to initiate fetches without page involvement (SW scope)
  • Mutation state tracking needs per-component reactivity (client scope)
  • IndexedDB writes for offline queue need to survive page navigation (SW could work for reads, but client writes are simpler and more reliable)

On this page