Service Worker Issues | Blue Frog Docs

Service Worker Issues

Diagnose and fix service worker registration, updates, and caching problems

Service Worker Issues

What This Means

Service workers are JavaScript files that run in the background, enabling:

  • Offline functionality
  • Background sync
  • Push notifications
  • Asset caching

Common issues include failed registration, stale cache, update problems, and scope conflicts.

How to Diagnose

1. Chrome DevTools

  1. DevTools > Application tab
  2. Click Service Workers in sidebar
  3. Check:
    • Registration status
    • Active/waiting workers
    • Error messages

2. Console Errors

// Check registration status
navigator.serviceWorker.getRegistrations().then(registrations => {
  console.log('Registered SWs:', registrations);
});

// Listen for errors
navigator.serviceWorker.register('/sw.js')
  .then(reg => console.log('Registered:', reg))
  .catch(err => console.error('Failed:', err));

3. Common Error Messages

Error Cause
"SecurityError" Not HTTPS or localhost
"Failed to register" SW file not found or syntax error
"Scope too wide" SW path issue
"Update found but waiting" skipWaiting not called

General Fixes

Basic Service Worker

// sw.js - Basic cache-first strategy
const CACHE_NAME = 'app-cache-v1';
const ASSETS = [
  '/',
  '/styles/main.css',
  '/scripts/app.js',
  '/offline.html'
];

// Install: cache assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(ASSETS))
      .then(() => self.skipWaiting())
  );
});

// Activate: clean old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      );
    }).then(() => self.clients.claim())
  );
});

// Fetch: cache-first strategy
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(cached => cached || fetch(event.request))
      .catch(() => caches.match('/offline.html'))
  );
});

Registration with Update Handling

// main.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js');

      // Check for updates
      registration.addEventListener('updatefound', () => {
        const newWorker = registration.installing;

        newWorker.addEventListener('statechange', () => {
          if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
            // New version available
            showUpdateNotification();
          }
        });
      });

      console.log('SW registered:', registration.scope);
    } catch (error) {
      console.error('SW registration failed:', error);
    }
  });
}

function showUpdateNotification() {
  if (confirm('New version available! Reload to update?')) {
    window.location.reload();
  }
}

Force Update on Change

// sw.js
const VERSION = '2.0.0';

self.addEventListener('install', (event) => {
  console.log('Installing SW version:', VERSION);
  self.skipWaiting(); // Don't wait for old SW to close
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    Promise.all([
      // Take control immediately
      self.clients.claim(),
      // Clear old caches
      caches.keys().then(keys =>
        Promise.all(keys.map(key => caches.delete(key)))
      )
    ])
  );
});

Network-First for API Calls

// sw.js - Different strategies for different requests
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);

  // API calls: network-first
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(networkFirst(event.request));
    return;
  }

  // Static assets: cache-first
  if (url.pathname.match(/\.(js|css|png|jpg|svg)$/)) {
    event.respondWith(cacheFirst(event.request));
    return;
  }

  // HTML: network-first with offline fallback
  event.respondWith(networkFirstWithOffline(event.request));
});

async function networkFirst(request) {
  try {
    return await fetch(request);
  } catch {
    return caches.match(request);
  }
}

async function cacheFirst(request) {
  const cached = await caches.match(request);
  return cached || fetch(request);
}

async function networkFirstWithOffline(request) {
  try {
    return await fetch(request);
  } catch {
    const cached = await caches.match(request);
    return cached || caches.match('/offline.html');
  }
}

Fix Stale Cache

// Force clear all caches
async function clearAllCaches() {
  const keys = await caches.keys();
  await Promise.all(keys.map(key => caches.delete(key)));
  console.log('All caches cleared');
}

// Unregister all service workers
async function unregisterAllSWs() {
  const registrations = await navigator.serviceWorker.getRegistrations();
  await Promise.all(registrations.map(reg => reg.unregister()));
  console.log('All service workers unregistered');
}

// Nuclear option: clear everything
async function hardReset() {
  await clearAllCaches();
  await unregisterAllSWs();
  window.location.reload(true);
}
// sw.js using Workbox
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';

// Precache static assets
precacheAndRoute(self.__WB_MANIFEST);

// Cache images
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
      }),
    ],
  })
);

// Network-first for API
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api-cache',
    networkTimeoutSeconds: 5,
  })
);

Scope Issues

// Service worker scope is determined by its location
// /sw.js -> scope is /
// /app/sw.js -> scope is /app/

// To expand scope (requires header):
// Service-Worker-Allowed: /
navigator.serviceWorker.register('/path/sw.js', {
  scope: '/'
});

Platform-Specific Guides

Platform Guide
Next.js Next.js Service Workers
WordPress WordPress PWA Plugins

Verification

  1. DevTools > Application > Service Workers shows "Activated"
  2. No console errors on registration
  3. Offline page works when network disabled
  4. Updates apply after refresh

Common Mistakes

Mistake Fix
Not using HTTPS Required (except localhost)
SW file 404 Check file path and server config
Caching too aggressively Use versioned cache names
Not calling skipWaiting Call in install event
Forgetting clients.claim Call in activate event
Caching API responses long-term Use network-first for dynamic data

Further Reading

// SYS.FOOTER