@tardstart/sw-cache
v1.0.0
Published
A configurable service worker caching layer for static-export web apps. Supports cache-first, network-first, and stale-while-revalidate strategies with versioned cache buckets and offline fallback.
Maintainers
Readme
@tardstart/sw-cache
A configurable service worker caching layer for static-export web apps (Next.js, Vite, etc.).
Features
- Three caching strategies — cache-first, network-first, stale-while-revalidate
- Versioned cache buckets — stamped with build version; old buckets are purged on activate
- Offline navigation fallback — walks path →
path.html→path/index.html→/index.html - Redirect normalisation — strips
opaqueredirect/ 3xx flags before handing torespondWith() - Configurable — no hardcoded paths, prefixes, or offline page copy
- Race-free state — concurrent callers of
syncCurrentVersionFromKeys()share one promise
Installation
npm install @tardstart/sw-cacheQuick start
// sw.ts (your service worker entry point)
import { init, onInstall, onActivate, onFetch } from '@tardstart/sw-cache';
init({
cachePrefix: 'my-app-v',
buildMapPath: '/build-map.json',
immutablePathPrefixes: ['/_next/static/'],
offlinePage: {
title: 'Offline',
body: '<p>No connection. Open the app once online to cache it.</p>',
backgroundColor: '#0a0a0a',
textColor: '#eee',
},
});
self.addEventListener('install', onInstall);
self.addEventListener('activate', onActivate);
self.addEventListener('fetch', onFetch);Configuration
All options are optional — defaults work for a standard Next.js static export.
| Option | Type | Default | Description |
|---|---|---|---|
| cachePrefix | string | 'sw-v' | Prefix for all versioned cache bucket names |
| buildMapPath | string | '/build-map.json' | Origin-relative path to the build manifest |
| immutablePathPrefixes | string[] | ['/_next/static/'] | Paths served cache-first without revalidation |
| offlinePage.title | string | 'Offline' | <title> of the fallback offline page |
| offlinePage.body | string | (generic message) | Inner <body> HTML of the fallback page |
| offlinePage.backgroundColor | string | '#050816' | CSS background colour |
| offlinePage.textColor | string | '#eee' | CSS text colour |
| fetchBuildMap | () => Promise<{version, files}> | fetches buildMapPath | Custom manifest resolver |
Build map format
By default the SW fetches buildMapPath and expects:
{
"version": "1775592092-abc1234",
"files": [
{ "path": "index.html" },
{ "path": "_next/static/chunks/main.js" }
]
}You can supply any async function as fetchBuildMap that returns this shape.
Strategies
| Strategy | Used for | Behaviour |
|---|---|---|
| cacheFirst | Immutable versioned assets | Cache → network → store → 503 |
| networkFirst | Build manifest | Network → cache → HTML fallback → 503 |
| staleWhileRevalidate | Everything else | Return cached immediately; revalidate in background |
| handleNavigate | Navigation + HTML requests | Network-first; falls back through path→html→index→offline page |
Advanced usage
All internal functions are exported for custom routing scenarios:
import {
handleNavigate,
cacheFirst,
networkFirst,
staleWhileRevalidate,
matchStoredHtmlDocument,
emergencyOfflineHtmlPage,
} from '@tardstart/sw-cache';Fixes over the original source
| Issue | Fix |
|---|---|
| Race condition in syncCurrentVersionFromKeys | In-flight promise deduplication (syncPromise ??= _doSync()) |
| getNavigationDocumentFromCaches duplicated caches.match | Delegates directly to matchStoredHtmlDocument |
| staleWhileRevalidate silently swallowed network errors | Logs with console.warn |
| 503 responses missing Content-Type header | Added Content-Type: text/plain; charset=utf-8 |
| isRedirectStatus included 304 Not Modified | Excluded 304 explicitly |
| handleNavigate used an IIFE wrapper | Converted to native async function |
| All constants hardcoded | All values read from SwConfig via getConfig() |
| /_next/static/ and build-map path hardcoded in routing | Configurable via immutablePathPrefixes and buildMapPath |
| install.ts tightly coupled to build-map.json schema | Accepts a fetchBuildMap callback |
| Offline page colour hardcoded | Configurable via offlinePage config |
