Performance

Caching with a service worker: what to cache and what to skip

A service worker serves assets from cache and leaves HTML to the network. This site has none today: /sw.js returns 404 and caching lives in the headers.

Key points

  • A service worker caches stable assets and leaves the HTML to the network.
  • On 1 November 2026 /sw.js answered 404: the site relies on HTTP headers alone.
  • A full precache would cover 65 files for 672,318 bytes, 212,752 gzipped.

A service worker caches assets that never change and leaves the HTML to the network, so a second visit does not download 65 files again. This site has none today: /sw.js answers 404 and caching lives only in the HTTP headers.

Four resources, four strategies

ResourceStrategyWhy
HTMLnetwork-first with a cached copyit changes on every publish
hashed assetscache-firstthe name changes on every build
/api/statsnetwork-first with a 3 s timeoutthe data must stay live
imagesstale-while-revalidateheavy, tolerant of staleness

The minimal worker

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("static-v1").then((cache) =>
      cache.addAll(["/", "/manifest.webmanifest"]),
    ),
  );
});

self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);
  if (url.pathname.startsWith("/api/")) return;
  event.respondWith(
    caches.match(event.request).then((hit) => hit || fetch(event.request)),
  );
});

Install opens a cache with a versioned name and puts the stable files in it; fetch answers from the cache and falls back to the network only when nothing matches. Requests to /api/ return early, so the data never turns stale.

The command

curl -sS -o /dev/null -w "%{http_code}\n" https://ui.eneawork.it/sw.js

The command prints 404 until the file exists: run it after a deploy, because a worker registered once stays active even when the file disappears from the server.

The limitation

Today all caching lives in the HTTP headers, and the measurement describes the ceiling of a precache, not a saving achieved. A badly configured worker serves stale HTML, and to switch it off you must publish a file that unregisters itself: deleting it from the server is not enough.

Where to continue

In the EneUI catalogue: 111,985 components, 1,556 blocks and the record sidebar, a component that does not change between visits. The guides: Cache-Control and immutable and route-level code splitting; the comparison Brotli versus gzip shows how much what you cache actually weighs.

Frequently asked questions

Do I need a service worker to have a cache?

No. HTTP caching with Cache-Control already covers HTML and assets and needs no code. A service worker adds offline control and programmable caching, but also a place where you can serve stale content.

Why not use cache-first for everything?

Because HTML and APIs change. With cache-first everywhere the first response served stays until you bump the cache version, even after a deploy.

How does a service worker update?

The browser downloads the file on every visit and activates the new version when every tab running the old worker closes. Change the cache name in install so old files are not reused.

Does a service worker make the first visit slower?

Yes, a full precache downloads every listed file at once. Limit it to critical assets and leave the rest to on-demand caching.

How do I remove it if something goes wrong?

Publish a worker that calls self.registration.unregister() and deletes the caches, then keep it live for at least one cycle of visits. Deleting the file from the server is not enough.