Build a trustworthy service-worker update
A safe service-worker release coordinates waiting versions, compatibility, unsaved work, multiple tabs, activation, cache migration, one reload, and rollback.
A service worker can update successfully while the product stays split across versions.
The browser may install a new worker and leave it waiting because an older worker still controls open tabs. One tab may show an update prompt, another may hold an unsaved draft, and a third may be offline. If the new worker activates immediately, its cache and request behavior can meet HTML or client state built for the previous version.
The current Service Workers specification models a registration with installing, waiting, and active workers and defines lifecycle events such as install and activate. Those states are the mechanism; the product still has to decide when a version may take control.
I want the page and worker to perform a small release handshake: announce the waiting version, check compatibility and local work, coordinate other tabs, request activation, observe controller change, and reload exactly once into a coherent asset graph.
An update should feel like preserving momentum, not randomly refreshing the application.
Registration listeners read a version manifest and expose update state without forcing activation or assuming every tab is idle.
Tabs report unsaved state, compatibility, connectivity, and leadership; the user chooses now or later when choice is safe.
Leader messages SKIP_WAITING, tabs observe controllerchange, one reload enters the new graph, caches reconcile, and telemetry closes the release.
Version the release surfaces
Track page bundle, worker, cache schema, local data schema, request protocol, and minimum compatible versions separately instead of relying on one build hash.
I would pressure-test that decision with four questions:
- Which versions can coexist?
- Which cache schema changes?
- Can old pages call the new worker?
- What requires reload?
The failure mode here is assuming a newly built worker is compatible with every open page. In progressive web apps and offline-capable sites where an active service worker may keep serving one asset graph while a newly installed worker waits, several tabs remain open, cached data schemas change, and users hold unsaved work, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a service-worker compatibility manifest. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.
The result I would look for is explicit safe and unsafe version combinations. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.
In practice, I would put a service-worker compatibility manifest beside the question “Which versions can coexist?” before the first implementation review. The next pass would use “Which cache schema changes?” to test the boundary, then “Can old pages call the new worker?” to expose the state most likely to be missed. I would keep “What requires reload?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports explicit safe and unsafe version combinations.
Detect a waiting worker
Registration updatefound, installing state changes, existing registration.waiting, and manual update checks should converge on one page-level update state.
The practical review starts here:
- Was a worker already waiting?
- Did install fail?
- When is update() requested?
- Which tab owns the prompt?
Those questions keep listening only after registration and missing an already waiting worker from becoming the default. I would capture the decision in a registration lifecycle adapter, then use it while the work is still cheap to change. For service-worker updates that preserve user work and version coherence, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.
Success would look like reliable update discovery across cold and warm starts. If I cannot point to that evidence, I have a direction, not a finished decision.
The implementation move is to make a registration lifecycle adapter part of the working surface. I would use it to answer “Was a worker already waiting?” while scope is still flexible, and “Did install fail?” before code or content becomes expensive to unwind. During QA, “When is update() requested?” and “Which tab owns the prompt?” become concrete checks rather than discussion prompts. That sequence turns service-worker updates that preserve user work and version coherence into something the team can operate and gives me a specific outcome to report: reliable update discovery across cold and warm starts.
- InstallingNew code is evaluated
Assets are fetched and verified; failure leaves the active worker untouched and produces a diagnosable install result.
- WaitingVersion is ready but not controlling
Open controlled clients keep the old worker active; the product can inspect readiness and preserve user work.
- ActivatedNew worker controls future clients
Cache cleanup and migration finish, controllerchange reaches pages, reload policy applies, and rollback monitoring begins.
Separate download from activation
Downloading code in the background is usually safe; taking control is a product transition that may need user consent, compatibility checks, and local-state preservation.
Before implementation, I would answer:
- Can activation wait?
- Is the update urgent?
- What work is at risk?
- Which deadline exists?
The artifact is an update urgency and activation policy. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is calling skipWaiting unconditionally during install; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.
For me, the useful receipt is fast preparation without surprising takeover. That connects an explicit service-worker release protocol that detects waiting versions, compares compatibility, coordinates tabs, protects local work, activates through a message handshake, migrates caches safely, reloads once, supports rollback, and emits a release receipt to an observable result instead of a process claim.
I would test this with one typical case and one boundary case. The typical case should make “Can activation wait?” easy to answer. The boundary should force a decision about “Is the update urgent?” and “What work is at risk?.” I would record both in an update urgency and activation policy, including the part that stayed unresolved after the first pass. The final check, “Which deadline exists?,” is where the artifact earns its place: it either supports fast preparation without surprising takeover, or it shows exactly why another iteration is needed.
Protect unsaved and queued work
Before activation, tabs should surface dirty forms, in-memory edits, uploads, transactions, offline mutations, and long-running tasks and either persist, finish, cancel, or defer them.
I would use these prompts during the working review:
- Which state exists only in memory?
- Can it be serialized?
- Will requests duplicate?
- How does restore work?
If the team slips into checking only whether a form input is focused, the product can still look complete while its operating rule stays ambiguous. I would make a pre-activation work inventory the shared reference and keep it small enough to update as evidence changes.
The standard is no user work lost across the version boundary. That tells me whether the decision helped the product, not merely whether the document was completed.
The working sequence is small: draft a pre-activation work inventory, review it against “Which state exists only in memory?,” implement the narrowest useful path, and then return with evidence for “Can it be serialized?.” I would use “Will requests duplicate?” to inspect product consequence and “How does restore work?” to decide whether the result is stable enough to ship. This keeps checking only whether a form input is focused visible as a known risk and makes no user work lost across the version boundary the release receipt rather than a hopeful conclusion.
| Signal | Decision | Working note |
|---|---|---|
| Compatible | Prompt can be deferred | Old page and new worker understand the same requests and data; users finish work and update at a natural boundary. |
| Coordinated | All tabs must transition | Cache or protocol changes require a leader, tab acknowledgements, draft preservation, and one synchronized takeover. |
| Critical | Update urgency is explicit | Security or data risk may shorten deferral, but the app still preserves recoverable work, communicates consequence, and provides fallback. |
Coordinate multiple tabs
One tab should lead the activation while every controlled client reports version and readiness through BroadcastChannel, client messaging, or another explicit protocol.
I would pressure-test that decision with four questions:
- How is a leader chosen?
- Which tabs must acknowledge?
- What if a tab is frozen?
- Can prompts duplicate?
The failure mode here is letting every tab call skipWaiting and reload independently. In progressive web apps and offline-capable sites where an active service worker may keep serving one asset graph while a newly installed worker waits, several tabs remain open, cached data schemas change, and users hold unsaved work, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a multi-tab update state machine. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.
The result I would look for is one coherent release decision across the origin. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.
In practice, I would put a multi-tab update state machine beside the question “How is a leader chosen?” before the first implementation review. The next pass would use “Which tabs must acknowledge?” to test the boundary, then “What if a tab is frozen?” to expose the state most likely to be missed. I would keep “Can prompts duplicate?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports one coherent release decision across the origin.
Activate through a message
The waiting worker should receive a version-scoped activation request, validate it, call skipWaiting, and report progress rather than exposing a magic side effect.
The practical review starts here:
- Is the target still waiting?
- Does version match the prompt?
- Can activation fail?
- What acknowledgement is returned?
Those questions keep posting an unversioned SKIP_WAITING string to any worker reference from becoming the default. I would capture the decision in a typed worker activation message, then use it while the work is still cheap to change. For service-worker updates that preserve user work and version coherence, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.
Success would look like a traceable request bound to the intended release. If I cannot point to that evidence, I have a direction, not a finished decision.
The implementation move is to make a typed worker activation message part of the working surface. I would use it to answer “Is the target still waiting?” while scope is still flexible, and “Does version match the prompt?” before code or content becomes expensive to unwind. During QA, “Can activation fail?” and “What acknowledgement is returned?” become concrete checks rather than discussion prompts. That sequence turns service-worker updates that preserve user work and version coherence into something the team can operate and gives me a specific outcome to report: a traceable request bound to the intended release.
Reload exactly once
Tabs should listen for controllerchange, use a per-navigation latch, and reload only after the new controller is present and saved state can be restored.
Before implementation, I would answer:
- Did controller actually change?
- Has this tab reloaded already?
- Can state restore after navigation?
- What if activation races startup?
The artifact is a controllerchange reload guard. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is reloading on both message acknowledgement and controllerchange; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.
For me, the useful receipt is one transition into a coherent version without loops. That connects an explicit service-worker release protocol that detects waiting versions, compares compatibility, coordinates tabs, protects local work, activates through a message handshake, migrates caches safely, reloads once, supports rollback, and emits a release receipt to an observable result instead of a process claim.
I would test this with one typical case and one boundary case. The typical case should make “Did controller actually change?” easy to answer. The boundary should force a decision about “Has this tab reloaded already?” and “Can state restore after navigation?.” I would record both in a controllerchange reload guard, including the part that stayed unresolved after the first pass. The final check, “What if activation races startup?,” is where the artifact earns its place: it either supports one transition into a coherent version without loops, or it shows exactly why another iteration is needed.
Migrate caches safely
New cache namespaces should be populated and verified before activation; old caches should be removed only when incompatible clients and rollback no longer need them.
I would use these prompts during the working review:
- Which assets are atomic?
- Can partial precache fail closed?
- When are old caches deleted?
- Can rollback still serve?
If the team slips into deleting every non-current cache at the start of activate, the product can still look complete while its operating rule stays ambiguous. I would make a cache migration and retention plan the shared reference and keep it small enough to update as evidence changes.
The standard is complete new assets with a bounded recovery path. That tells me whether the decision helped the product, not merely whether the document was completed.
The working sequence is small: draft a cache migration and retention plan, review it against “Which assets are atomic?,” implement the narrowest useful path, and then return with evidence for “Can partial precache fail closed?.” I would use “When are old caches deleted?” to inspect product consequence and “Can rollback still serve?” to decide whether the result is stable enough to ship. This keeps deleting every non-current cache at the start of activate visible as a known risk and makes complete new assets with a bounded recovery path the release receipt rather than a hopeful conclusion.
Design offline behavior
A client that learns about an update and then loses connectivity needs clear deferral, cached update information, compatible offline routes, and a retry that does not trap it between versions.
I would pressure-test that decision with four questions:
- Can activation finish offline?
- Are required assets cached?
- Which data schema is local?
- When is the prompt shown again?
The failure mode here is offering Update now when the new asset graph cannot be completed. In progressive web apps and offline-capable sites where an active service worker may keep serving one asset graph while a newly installed worker waits, several tabs remain open, cached data schemas change, and users hold unsaved work, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an offline update decision table. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.
The result I would look for is offline users remaining functional and recoverable. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.
In practice, I would put an offline update decision table beside the question “Can activation finish offline?” before the first implementation review. The next pass would use “Are required assets cached?” to test the boundary, then “Which data schema is local?” to expose the state most likely to be missed. I would keep “When is the prompt shown again?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports offline users remaining functional and recoverable.
Test the real lifecycle
QA should cover first install, waiting worker, failed install, dirty and clean tabs, frozen tab, offline transition, incompatible schema, controllerchange race, reload loop, cache cleanup, rollback, and browser restart.
The practical review starts here:
- Can work survive?
- Does only one reload occur?
- Do tabs converge?
- Can the old version return safely?
Those questions keep testing updates by clicking refresh until new content appears from becoming the default. I would capture the decision in a multi-context service-worker release suite, then use it while the work is still cheap to change. For service-worker updates that preserve user work and version coherence, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.
Success would look like evidence that browser lifecycle and product lifecycle agree. If I cannot point to that evidence, I have a direction, not a finished decision.
The implementation move is to make a multi-context service-worker release suite part of the working surface. I would use it to answer “Can work survive?” while scope is still flexible, and “Does only one reload occur?” before code or content becomes expensive to unwind. During QA, “Do tabs converge?” and “Can the old version return safely?” become concrete checks rather than discussion prompts. That sequence turns service-worker updates that preserve user work and version coherence into something the team can operate and gives me a specific outcome to report: evidence that browser lifecycle and product lifecycle agree.
What I would show in the work
The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:
- a service-worker compatibility manifest
- a registration lifecycle adapter
- an update urgency and activation policy
- a pre-activation work inventory
- a multi-tab update state machine
I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because an explicit service-worker release protocol that detects waiting versions, compares compatibility, coordinates tabs, protects local work, activates through a message handshake, migrates caches safely, reloads once, supports rollback, and emits a release receipt becomes harder to understand when every process detail is treated as equally important.
I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.
# release app 42 / worker 17 / cache schema 6 Compatible pages 40–42, minimum data schema 5, generated asset digest, build time, urgency normal, rollback worker 16 retained.
# clients 3 tabs / 1 dirty / 1 offline Leader elected through BroadcastChannel; dirty tab persists draft and acknowledges; offline tab defers until connectivity returns.
# receipt activate 17 / reload once / cache old removed controllerchange observed, requests use schema 6, draft restored, error rate stable, rollback window expires after 30 minutes.
Resource path
The practical follow-up I would build is a service-worker update-flow starter with registration listener, version manifest, waiting-worker prompt, BroadcastChannel coordination, unsaved-work guard, skip-waiting message, controllerchange reload latch, cache namespaces, migration hooks, offline policy, rollback switch, telemetry, and multi-tab test matrix. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.
The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.
Review checklist
The article-specific review questions are:
- Which versions can coexist?
- Was a worker already waiting?
- Can activation wait?
- Which state exists only in memory?
- How is a leader chosen?
- Is the target still waiting?
- Did controller actually change?
- Which assets are atomic?
- Can activation finish offline?
- Can work survive?
I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.
Implementation notes
For service-worker updates that preserve user work and version coherence, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.
The proof signals I care about are specific to this article:
- a traceable request bound to the intended release
- one transition into a coherent version without loops
- complete new assets with a bounded recovery path
- offline users remaining functional and recoverable
- evidence that browser lifecycle and product lifecycle agree
I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.
The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.
Case-study packaging
I would structure the case-study version around the four visual lessons already established:
- The page and worker coordinate a controlled takeover.
- Update state spans more than install success.
- Release policy depends on compatibility.
- A version manifest makes the handshake testable.
The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.
I would include one caveat tied to progressive web apps and offline-capable sites where an active service worker may keep serving one asset graph while a newly installed worker waits, several tabs remain open, cached data schemas change, and users hold unsaved work: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.
The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.
Interview angle
In an interview, I would explain this through an explicit service-worker release protocol that detects waiting versions, compares compatibility, coordinates tabs, protects local work, activates through a message handshake, migrates caches safely, reloads once, supports rollback, and emits a release receipt. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.
The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.
The hiring signal
A safe service-worker update protocol is a hiring signal because it shows I can connect browser lifecycle, offline state, multi-tab coordination, cache compatibility, release UX, rollback, and production observability.
That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.
Use this after reading.
Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.
Front-End State Recipes
Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.
UI PR Risk Review Checklist
A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.
Handoff Notes Template
A build-ready handoff format for scope, states, interactions, open questions, analytics, and QA.