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.
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