Making a site installable is supposed to be easy: a manifest, a service worker, done. On a content site, maybe. On a tool site — where the whole point is fresh data from the network on every single page load — a careless service worker breaks the thing people came for. I learned this setting up the installable web app on ramrom.io, my video downloader, and the fix was choosing the right caching strategy for each kind of request.
Why network-first, not cache-first
Most service worker tutorials default to cache-first: serve the cached copy, fall back to network. That is perfect for a blog. It is wrong for a tool page.
A downloader page has two kinds of network traffic. The static shell — CSS, icons, the offline page — changes maybe once a month. The API calls — resolving a pasted URL, fetching fresh media links — must be live every single time. Provider media URLs are often signed and expire within minutes; a cache-first worker happily serving a cached resolve response hands the visitor a dead link with total confidence.
So the worker on ramrom.io is network-first for navigation and API requests, cache-first only for versioned static assets. Three rules in the fetch handler:
- Requests to
/api/*are network-only. Never cached. A "Service is busy" error from five minutes ago must not be served to the next visitor. - Navigation requests (the page itself) go to the network first, and only fall back to a small offline page if the network is truly gone.
- Static assets — CSS, JS, icons with
?v=in the name — are cache-first, because the version string already handles invalidation for me.
The one-file-per-origin trap
There is a gotcha nobody warns you about: an origin gets exactly one active service worker at the root scope. Any third-party script that asks you to install their sw.js at the root replaces yours — the installable app, the offline page, all of it, gone in one deploy. A push-ad network offered me extra revenue and required exactly that: their worker at /sw.js, mine deleted. I said no, because the worker is mine. If you are ever told "just upload our sw.js", know what you are giving up.
Updates without breaking a download in progress
A worker only checks for a new version on navigation, and by default the new one waits until all tabs close. That polite behaviour is actually what you want on a tool site: forcing skipWaiting mid-session could pull the rug out from under a visitor mid-download. I bump the cache name on each deploy and let the new worker take over on the next visit. No drama.
The honest verdict
Installability is worth it — "Add to home screen" on Android turns a tool someone visits twice a week into an app icon. But a service worker on a tool site is not a performance trick; it is a promise about freshness. Cache the shell, never the data, keep the worker yours, and let updates land between sessions instead of during them. The code is maybe sixty lines. Getting the strategy right is the actual work.



