Lesson 0002 · ~12 minutes

Registration, scope & first lifecycle

One skill: explain register()installactivatecontrol for the first service worker, including scope and why the registering page often is not controlled yet.

Win for this lesson

You can sketch a correct first-SW path in a design review: where the script lives, what scope you get, when to call register, what waitUntil does on install, and why “registered” ≠ “this tab is controlled.”

1. Register is a page-side call

From a secure page (HTTPS or localhost), feature-detect and register the worker script. The API is navigator.serviceWorker.register():

if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker
      .register("/sw.js")
      .then((reg) => console.log("scope:", reg.scope))
      .catch((err) => console.error(err));
  });
}
Default timing rule

First visit cares about time-to-interactive. Delay register() until after load (or after your framework’s boot) unless you have a measured reason to register early (e.g. aggressive runtime caching with clients.claim()).

2. Scope is about the script’s path

A service worker only controls clients under its scope. Default scope is ./ relative to the service worker file URL (lifecycle article, Learn PWA):

You can pass { scope: "..." }, but you cannot claim a wider scope than the browser allows for that script location. Practical rule: put the SW script at the highest path you need to control (often site root for a whole SPA).

3. First lifecycle: install → activate

Once register() succeeds at downloading and parsing the script, the browser runs the lifecycle. For the first service worker (no previous version), Jake Archibald’s model is:

Download

Fetch SW script. Parse/execute failure rejects registration; worker is discarded.

install

First event, once per SW version. Precache shell here. Extend with event.waitUntil(promise).

activate

Ready for functional events. Ideal later for deleting old caches when a new version replaces an old one.

Control?

Default: the page that called register stays uncontrolled until refresh/reopen. Consistency first.

// sw.js
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("static-v1").then((cache) =>
      cache.addAll(["/", "/styles.css", "/app.js"])
    )
  );
});

self.addEventListener("activate", (event) => {
  // first SW: often nothing; updates clean old caches here
});

If the promise passed to waitUntil during install rejects, installation fails and that service worker never controls anything. That is intentional: you can treat precached assets as install-time dependencies (lifecycle).

4. Registered ≠ controlling this tab

This is the #1 first-visit confusion:

Archibald’s dog/cat demo: the first load still hits the network for a subresource even after activate, because the document itself did not load under the SW. Refresh, and both document and subresources go through fetch handlers.

clients.claim() (awareness)

On activate, self.clients.claim() can take over already-open uncontrolled pages. Useful for early runtime caching; risky if the SW serves a different world than a plain network load. Not required boilerplate — know it exists, default to consistency.

5. Practice

Choose the best answer. Options matched for length. Immediate feedback.

Scenario A

You register /app/sw.js with no scope option. Which clients can it control by default?

Scenario B

First visit: register('/sw.js') resolves; install and activate fire. Still, controller is null. Why?

Scenario C

Install handler: waitUntil(cache.addAll([...])) and one URL 404s so the promise rejects. Result?

Scenario D

Mobile-first SPA, heavy first paint, precache list is large. Best default for calling register?

6. What to remember

Ask your teacher Scope fights with CDNs, “why is controller still null?”, or where to put sw.js in a Vite/webpack app — ask in chat.

Primary source (read next)

web.dev — The service worker lifecycle (Jake Archibald) Read the “first service worker” sections (install, activate, scope, control, clients.claim). Skip or skim “Updating” until lesson 3 if short on time. Secondary: Service worker registration (when to call register).