Caching is a key aspect of performance optimization in any application. The browser has an in-built cache for images, scripts, styles, fonts, and more—but like most caching, it helps most from the second interaction onward. Service workers and the Cache API let you precache assets during install, often in parallel with the page, so first-time visitors can benefit too (especially for later requests on the same visit), not only on repeat loads.
Every browser ships with a cache for images, scripts, styles, fonts, and other static assets. That helps repeat visits feel fast—yet on a first visit you still depend heavily on the network. Service workers and the Cache API offer more control: you can precache critical files during install, choose how requests are served, and keep users productive when the network is slow or unavailable.
Unlike the HTTP cache alone, you are not limited to helping only the next navigation. Once your page registers the worker, install and precache run on a separate thread in parallel with the UI. A first-time visitor can still benefit on that same visit—for example when lazy-loaded routes, images, or chunks are requested after precache completes—and return visitors get an even faster experience from a warm Cache Storage layer.
A service worker is a script that runs on its own thread, separate from your page’s main UI. It sits between your app and the network like a programmable proxy. That makes it a natural place to cache responses, handle push notifications, or serve an offline fallback page. Because it is not tied to a single tab, it can keep working in the background—even after the user closes the browser—until the browser decides to stop it.

A service worker does not take control the moment you register it. It moves through install → waiting → activate, and only then can it intercept fetches for pages under its scope. Understanding that lifecycle matters when you ship updates: a new worker may sit in waiting until all tabs using the old one are closed.

The browser already caches static assets, so why add another layer? In practice, service-worker caching gives you control the HTTP cache alone does not:
The Cache API stores pairs of Request and Response objects—exactly what you need when intercepting fetches. IndexedDB is a separate store for larger or structured data. Both are available in the window and in the service worker, which is why libraries like Workbox often use IndexedDB for metadata (for example, when an entry should expire) while keeping actual HTTP responses in Cache Storage.

Workbox is Google’s library on top of the native service worker APIs. It does not replace the platform—it packages common patterns so you spend less time on boilerplate and edge cases.
You get sensible defaults for routing and caching strategies, expiration plugins backed by IndexedDB, built-in cache versioning for precached assets, and tooling that makes debugging production caches less painful.
Pre-caching means registering a list of URLs (a precache manifest, often with revision hashes) during the install event. The browser fetches those URLs and stores them in Cache Storage while install runs on the service worker thread—in parallel with whatever the page is still loading, once registration has started.
Workbox integrates cleanly with bundlers like webpack via plugins that generate the manifest from your build output.
On a visitor’s first load of your origin, precaching does not help the earliest assets by default:
navigator.serviceWorker.register(...).So the main bundle and shell assets for that first navigation are often already in flight—or finished—before install completes. Until the worker is active and controlling the client, fetch events for that document are not served from Cache Storage anyway. A new worker may also sit in waiting until tabs reload unless you use skipWaiting() and clients.claim().
That does not make precaching useless. It means you should set expectations correctly:
<head>), plus skipWaiting() and clients.claim(), can widen the window for same-visit cache hits, but critical-path resources on a true first visit are still a race with the network.The separate worker thread avoids blocking the UI; it does not mean precache runs from the first millisecond of navigation.
Not everything can be listed at build time—especially in a micro-frontend setup where chunk URLs appear at runtime. Runtime caching fills the cache as users navigate: the first request may hit the network, later requests can be served from disk. That pattern is often a better fit for first-visit wins on unknown URLs than precache alone. Workbox exposes the same strategy primitives for both precache and runtime routes.
Workbox ships with named strategies you can compose or extend:
| Strategy | Behavior |
|---|---|
| Cache only | Always respond from cache; network is not used. |
| Network only | Always go to the network; nothing is read from cache. |
| Cache first | Try cache, fall back to network on miss. |
| Network first | Try network, fall back to cache on failure or timeout. |
| Stale while revalidate | Return cache immediately, update cache in the background. |
Pick a strategy per route: shell and fonts might be cache-first; API calls might be network-first; marketing images might be stale-while-revalidate.
Cache Storage is not unlimited. Browsers typically grant a share of available disk space (often discussed as a fraction of free space on the device). The exact quota is implementation-defined and can vary—Safari on iOS has historically been stricter than desktop Chrome. When storage pressure grows, the browser may evict entries; policies differ, but you should not assume your cache lives forever without a plan.
Shipping a new service worker is your main lever for invalidation: bump the precache manifest (or cache name) on each release so old entries are replaced. In production we usually:
Service workers are powerful, but they come with constraints worth planning for:
skipWaiting and clients.claim deliberately).opaque responses have limited introspection).localStorage and other sync APIs are unavailable in the worker context.