Guide · @staticflux/pwa · @staticflux/hydrate
PWA & hydration
Instant boot from three directions: a service worker built from the chunk manifest, islands that hydrate prerendered HTML on demand, and state snapshots restored before the network answers.
#Service worker and updates
@staticflux/pwa/sw runs inside the service worker: an atomic precache of the critical tier from sf-manifest.json (serviceWorkerConfigFromManifest), a warm cache that likely / speculative warmups fill at low priority, cache strategies per rule (Wasm, locale catalogs, lazy assets), network-first navigations with an app-shell fallback, and optional plugins (background sync of idempotent mutations, push, share target, COOP/COEP injection for static hosts).
On the page, createPwaUpdater({ env, scriptUrl, frameworkMajor, reinitialize, prompt }) registers the worker and runs the update flow: a new version → a messaging prompt → skip-waiting only on accept (chunk versions never mix) → supervisor.reinitialize() without a reload. A framework-major change or an unknown version falls back to a full reload — the explicit escape hatch.
const updater = createPwaUpdater({
env: browserPwaPageEnv(),
scriptUrl: '/sw.js',
frameworkMajor: 0,
reinitialize: { reinitialize: () => supervisor.reinitialize() }, // ReinitializePort
prompt: { offer: (info, signal) => askToUpdate(info, signal) }, // UpdatePromptPort → 'accept' | 'dismiss'
});
await updater.register();
Launch data — links, files, share targets, notification clicks — arrives as LaunchIntents through a port, as data only (prefill, never executed). defineWebAppManifest validates the web app manifest at build time.
#Islands over prerendered HTML
<!-- build time: renderIslandMarker({ chunk: 'island:code', hydrate: 'interaction', html }) -->
<sf-island chunk="island:code" hydrate="interaction">
<figure class="code">…<button data-copy>Copy</button>…</figure>
</sf-island>
// the island chunk: a named hydrate export (or defineIsland({ props, hydrate }))
export const hydrate = defineIsland({
async hydrate(element, { injector, scope, signal }) {
const button = element.querySelector('[data-copy]')!;
scope.listen(button, 'click', () => copy(element)); // everything in the island scope
return ok();
},
}).hydrate;
// boot: one hydrator for the page, islands load through the chunk loader (gated)
createIslandHydrator({ loader, telemetry }).start(document.body, { injector: app.injector });
- Strategies:
visible(default),idle,interaction(preloaded on hover / focus, hydrated on first input),eager(requires areason),never. - Event replay: clicks, input and submits before hydration are queued and replayed on the original targets afterwards.
- The island gate and
loader.checkrun before any fetch; a denied island isskippedand its chunk never requested. - Static markup is the first render; hydration is “interactive” — both are feature marks, and a size mismatch after hydration is reported as a layout-shift risk.
#State rehydration
createSnapshotRegistry persists store and resource snapshots to an AsyncStore and restores them at boot before the network — versioned, identity-bound (opaque hashes), budgeted and time-boxed (default 200 ms). Restored data is tagged snapshot and revalidated (SWR); a restore never bumps the epoch, so old-epoch responses are still discarded. Snapshot failures never fail boot.
AGENTS.md on GitHub: @staticflux/pwa · @staticflux/hydrate · @staticflux/loader