Optional Standard Static MPA Service Worker

Status: Proposal validated by example

examples/static-mpa-offline is the current domstack-native prototype for a possible optional standard static MPA service-worker preset.

It no longer runtime-fetches domstack-manifest.json or a generated policy JSON file.

Instead, hooks.manifestBuilt injects the finalized manifest-shaped policy into /service-worker.js with defineServiceWorkerConstant().

The service worker consumes Domstack manifest entries directly and derives cache behavior from the manifest fields and selected offline vars.

Goals

Non-goals

Current example behavior

The vanilla example has these moving parts:

The service worker uses:

Offline vars convention

The example intentionally keeps user-facing vars small:

type StaticMpaOfflineManifestVars = {
  offline?: boolean
  precache?: boolean
}

offline: true means the page/route is allowed to become available offline.

offline: false makes the route network-only with offline fallback behavior for navigations.

precache: true means the navigation page is cached during install.

precache: false means the navigation page is runtime-cached after the first successful visit.

Layout vars set section defaults.

Page vars/frontmatter can override layout vars through the normal cascade.

The cascade is:

page vars -> layout vars -> global vars -> defaults

Build-time injection model

The current build model is:

final Domstack manifest
  -> manifestBuilt hook
  -> context.defineServiceWorkerConstant('__DOMSTACK_SERVICE_WORKER_POLICY__', policy)
  -> final /service-worker.js bundle

This is preferred over:

Policy changes change /service-worker.js bytes and trigger the browser update lifecycle.

Watch mode

Watch mode does not produce a manifest policy.

The service worker detects that the injected policy constant is missing and installs as a no-op cleanup worker.

The watch worker:

The browser client also unregisters workers and clears known caches in watch mode.

This double layer matters because a previous production worker can serve cached HTML/JS before the watch-mode client code runs.

Watch builds disable esbuild splitting so /service-worker.js stays self-contained during cleanup.

Client registration helper behavior

A future reusable client helper should:

  1. No-op when navigator.serviceWorker is unavailable.
  2. Clean up when DOMSTACK_MANIFEST_ENABLED is false.
  3. Register after window.load by default.
  4. Register with the stable service-worker URL/scope from Domstack defines.
  5. Use { type: 'module', updateViaCache: 'none' }.
  6. Detect installing, waiting, and active states immediately after registration.
  7. Expose callbacks/events for ready, update available, updating, reset, error, and online/offline state.
  8. Avoid hard-coded blocking dialogs.
  9. Provide a default reset query param such as ?reset-sw.
  10. Reload once on controllerchange after an accepted update.

Possible reusable API

Start with reusable imports rather than generated service-worker source:

// src/service-worker.ts
import '@domstack/static/service-worker/static-mpa'
// src/global.client.ts
import { registerDomstackServiceWorker } from '@domstack/static/client/service-worker'

registerDomstackServiceWorker()

This keeps service workers inspectable and customizable.

A higher-level preset can come later if the helper API stabilizes.

Recovery design

Every standard path should include two recovery tiers.

Recoverable reset

If page JS still loads, a query param should reset worker state:

/?reset-sw

Behavior:

  1. Post RESET_SERVICE_WORKER to active/waiting/installing workers.
  2. Unregister matching registrations.
  3. Delete known domstack cache prefixes.
  4. Remove the reset query param.
  5. Reload from the network.

Emergency replacement worker

A rescue worker can be deployed at the exact production service-worker URL:

/service-worker.js

It should:

The exact URL requirement is important.

Deploying a rescue worker at a different URL leaves the broken worker active.

Open questions