All articles
· 7 min read

Workbox vs a custom service worker: honest trade-offs

When Workbox is the right call, when a 40-line hand-rolled service worker beats it, and the maintenance burden nobody talks about.

TI
The InstantPWA team
Product & engineering

Workbox is Google’s library for building service workers. It’s used by every major framework template (Next.js, Nuxt, Angular, Astro). It’s also overkill for many projects. Here’s the honest comparison after shipping both in production.

Pick Workbox when you need

  • Multiple caching strategies per URL pattern.
  • Precache manifests generated at build time (Vite/Webpack plugin).
  • Automatic cache expiration and quota management.
  • Background sync retry queues.

Hand-roll when

  • Your site is static or near-static.
  • You need one caching strategy for everything.
  • Bundle size matters (marketing sites, LCP-sensitive pages).

The 40-line worker that ships everywhere

const CACHE = 'v3';
const APP_SHELL = ['/', '/manifest.webmanifest', '/icon-192.png'];

self.addEventListener('install', (e) => {
  e.waitUntil(caches.open(CACHE).then((c) => c.addAll(APP_SHELL)));
  self.skipWaiting();
});

self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
  );
  self.clients.claim();
});

self.addEventListener('fetch', (e) => {
  const req = e.request;
  if (req.method !== 'GET') return;
  e.respondWith(
    caches.match(req).then((cached) => {
      const fresh = fetch(req).then((res) => {
        const copy = res.clone();
        caches.open(CACHE).then((c) => c.put(req, copy));
        return res;
      }).catch(() => cached);
      return cached || fresh;
    })
  );
});

The Workbox equivalent

import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';

precacheAndRoute(self.__WB_MANIFEST);
registerRoute(({ request }) => request.destination === 'image', new StaleWhileRevalidate({ cacheName: 'images' }));

The hidden cost of Workbox

Workbox’s manifest injection changes every build, which means the service worker file hash changes every deploy. Clients update aggressively — great for freshness, painful when you deploy 10×/day and users get stuck in a re-download loop. Add a debounce (maxAgeSeconds on Cache-Control) or clients thrash.

Our recommendation

Ship the 40-line worker first. Move to Workbox the day you write your third caching strategy. Never write your own precache manifest generator — that’s the one thing Workbox does 10× better than any hand-rolled solution.

Ship your install prompt in 60 seconds

InstantPWA is one line of code. Free forever for one widget, no card required.

Get started free