Why registration timing matters
Service workers can dramatically improve the experience of repeat visits, but their initial setup is easy to get wrong. The problem isn’t the code itself — the standard boilerplate for registration is short and well understood. The issue is when that registration runs relative to everything else the browser is doing on a user’s first visit.
On that first visit, there is no service worker yet, and the browser has no way to know one is coming. Your job is to get the critical resources on screen as fast as possible. If, in the middle of that download, the browser also starts a new background thread for the service worker — and that thread immediately begins fetching its own resources — you’ve introduced contention for both CPU and, more importantly, bandwidth. On a low-end mobile device or a slow connection, this can push time-to-interactive well past what it needs to be.
The standard registration boilerplate
The registration snippet you see in most tutorials looks something like this:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
Sometimes it’s dressed up with console.log() statements or a small update-detection routine that prompts the user to refresh, but the underlying shape is the same. The nuance is not in the API call itself — navigator.serviceWorker.register() is straightforward. The real question is where you place that call in your page’s lifecycle.
Deferring registration until after load
The simplest improvement is to wait until the load event fires on window before calling register():
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
The load event fires when the page and all its subresources have finished downloading, giving the critical path a clear head start. But the right moment can also depend on what your app does after load. The Google I/O 2016 web app, for example, plays a short animation before showing its main screen. The team found that starting registration during that animation caused jank on low-end devices, so they delayed it until the animation finished and the browser had a few idle seconds.
If your app uses a framework that performs post-load setup, look for a framework-specific event that signals when that work is done. The general principle is the same: register when the browser is likely to be idle, not when it’s already busy.
What about repeat visits?
Deferring registration on the first visit has no effect on subsequent visits. Once a service worker is installed and activated, it starts before any request for a page under its scope is made — otherwise it couldn’t intercept navigation requests in the first place.
For repeat visits, the timing of navigator.serviceWorker.register() is irrelevant. If the service worker script URL hasn’t changed, the call is effectively a no-op; the browser already has an active registration. Whether you call it early or late doesn’t change the outcome.
One case for early registration
There is a scenario where registering as early as possible makes sense: when the service worker uses clients.claim() to take control of the page during the first visit, and it aggressively performs runtime caching in its fetch handler. In that case, an early activation helps populate runtime caches sooner. If you’re doing this, though, be careful that your install handler doesn’t make requests that compete with the main page’s critical resources.
Testing your first-visit experience
The easiest way to simulate a first visit is to open your app in an Incognito window and watch the network traffic in DevTools. If you’re constantly reloading a local instance, your service worker and caches are already warm — you’re not seeing what a new user sees.
Two screenshots from a sample app illustrate the difference. With immediate registration, the service worker’s precache requests (shown with a gear icon in DevTools) are interspersed with the page’s own resource requests:
When registration is delayed until after page load, the precache requests start only after all network resources have been fetched, eliminating any contention:
You’ll also notice that some precache items are served (from disk cache) — the service worker can populate its cache without hitting the network again. For a more realistic test, run the same check on an actual low-end Android device connected via USB, using Chrome’s remote debugging tools with network throttling enabled.
A practical baseline
The goal is simple: protect the first-visit experience by keeping the service worker from competing with the page’s initial resources. Delaying registration until after the page has loaded achieves that while preserving all the benefits of the service worker on repeat visits.
The cleanest way to enforce this pattern is:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}



