Without a Service Worker caching strategy, every visit to your site requires a full round-trip to the origin for every asset (ISO-25010 time-behaviour). On return visits — where a Service Worker could serve assets from cache in under 1ms — users experience full network latency instead. A missing cache versioning strategy means that when you deploy, old Service Worker caches persist and serve stale JavaScript, CSS, or HTML until the user manually clears their cache. Stale Service Worker caches are one of the hardest classes of production bug to diagnose.
Low because Service Worker absence degrades return-visit performance and offline resilience but does not break initial functionality on the live network.
Implement a Service Worker with cache versioning. The version constant forces cache invalidation on every deploy:
// public/sw.ts
const CACHE_VERSION = 'v2' // increment on every deploy
const CACHE_NAME = `app-${CACHE_VERSION}`
const PRECACHE_ASSETS = ['/index.html', '/styles/main.css', '/js/app.js']
self.addEventListener('install', (e: ExtendableEvent) => {
e.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(PRECACHE_ASSETS))
)
})
self.addEventListener('activate', (e: ExtendableEvent) => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
)
)
})
self.addEventListener('fetch', (e: FetchEvent) => {
e.respondWith(
caches.match(e.request).then(cached => cached ?? fetch(e.request))
)
})
For Next.js projects, use next-pwa or Workbox to generate Service Worker configuration automatically with minimal manual maintenance.
ID: performance-core.script-style-efficiency.service-worker
Severity: low
What to look for: Count all relevant instances and enumerate each. Check for Service Worker file (sw.js, service-worker.js, etc.) in the project. Look for caching strategy: cache-first, network-first, or stale-while-revalidate. Check whether the strategy is documented and version management is in place for cache invalidation.
Pass criteria: A Service Worker is registered with a defined caching strategy. At least 1 implementation must be verified. Cache versioning is implemented to invalidate old caches on deploy. Strategy matches use case (cache-first for static assets, network-first for dynamic content, stale-while-revalidate for APIs).
Fail criteria: No Service Worker found, or Service Worker exists but has no caching strategy or versioning. Old caches persist after deploy, serving stale content to users.
Skip (N/A) when: The project is not deployed to production or offline functionality is not a requirement.
Detail on fail: Describe the caching gap. Example: "No Service Worker found; offline support and caching not implemented" or "Service Worker caches all requests with no expiration; users see stale content after deploy until manual cache clear".
Remediation: Service Workers enable offline support and caching:
// sw.ts
const CACHE_NAME = 'app-v1'
const ASSETS = ['/index.html', '/styles.css', '/app.js']
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
)
})
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
// Cache-first for static assets
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((res) => {
const cache = caches.open(CACHE_NAME)
cache.then((c) => c.put(event.request, res.clone()))
return res
})
})
)
})
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((names) => {
return Promise.all(
names.filter((name) => name !== CACHE_NAME).map((name) => caches.delete(name))
)
})
)
})