JORVEL

Service Worker

JORVEL ships a Service Worker that caches the app shell, every remoteEntry.js, and federation chunks. Second visits (or flaky networks) load instantly from cache. The SW is opt-in — generate it once, register it from your shell bootstrap, and the runtime handles updates.

Use this in production only

Service Workers cache aggressively. In dev they fight HMR. The CLI does not register one for you — make the registration call conditional on process.env.NODE_ENV === 'production'.

Generate

bash
jorvel sw generate
# discovers the host app → writes <host>/public/jorvel-sw.js

jorvel sw generate --app shell   # or target an app explicitly

With no --app, the command discovers the workspace host app (honoring appsDir) so you don't hardcode a folder name. The generated SW reads the build manifest at install time and pre-caches the shell. Re-run the command on every build, or wire it into your build script.

Register

apps/shell/src/bootstrap.tsx
tsx
import { registerJorvelServiceWorker } from '@jorvel/runtime';

if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
  registerJorvelServiceWorker({
    url: '/jorvel-sw.js',
    autoActivate: true,
    onUpdateReady: () => showUpdateBanner(),
    onError: (err) => observability.reportError(err),
  });
}

Cache strategy

Asset classStrategyRationale
remoteEntry.js + /jorvel/remotes/**stale-while-revalidateCache-first for instant load; revalidate in background so the next visit gets new code
Fingerprinted chunks *.[hash].jscache-firstContent-addressable — never changes for a given URL
HTML documentsnetwork-first with offline fallbackAlways try fresh; serve cached /offline.html if offline
API requests (/api/**)not cachedThe SW gets out of the way for dynamic data

Update flow

  1. User visits — the browser fetches /jorvel-sw.js in the background.
  2. If the bytes changed, the new SW installs in parallel and waits in installed state.
  3. The runtime fires onUpdateReady. Show a banner: "A new version is available. Reload?"
  4. User clicks reload → runtime sends SKIP_WAITING; new SW activates and takes control of all open clients. autoActivate: trueskips the user prompt and activates immediately — fine for apps where users don't have unsaved state, risky otherwise.

Register options

OptionDefaultPurpose
urlRequired. Path to the SW file.
scopesame as url directoryRestricts which URLs the SW controls.
autoActivatefalseSkip the waiting state; new SW activates immediately.
onUpdateReadyCalled when a new SW is installed and waiting.
onActivatedCalled when the new SW takes control.
onErrorRegistration / install errors.

Unregister

ts
import { unregisterJorvelServiceWorker } from '@jorvel/runtime';
await unregisterJorvelServiceWorker();

Use this from a console command for stuck users, or as a kill-switch wired to a feature flag.

Scope note

Service Workers are restricted to the origin root by default. When serving remotes on a CDN subdomain, register a separate SW per origin or proxy remotes under the host origin via jorvel dev --proxy-remotes / a production reverse proxy. The Service-Worker-Allowedresponse header is the only way to widen scope beyond the SW file's directory.

Debugging

  • DevTools → Application → Service Workers → check "Update on reload" while iterating on the SW source.
  • To force a refresh, click Unregister then hard-reload. Or call unregisterJorvelServiceWorker() from the console.
  • Caches show up under Application → Cache Storage. Names start with jorvel:; entries include the URL and expiry timestamp.
  • To simulate offline, DevTools → Network → throttle dropdown → "Offline". The SW should serve the cached shell + offline fallback.
  • Console messages from the SW go to a different log surface — open the SW source from the Application panel and click "inspect" to attach a console.

CSP impact

Service Workers need to be served same-origin with Service-Worker-Allowed: /if you want broader scope than the file's directory. They also need script-src 'self' at minimum — strict-dynamic nonces do not apply to top-level script registration; only to inline scripts.

Caching CDN-hosted remotes

If your remotes live on cdn.acme.com, the same-origin SW cannot cache them directly. Two options:

  1. Proxy remotes under the host origin. The Rspack dev-server already does this via --proxy-remotes; in production, a CDN edge function or reverse proxy can replicate the pattern.
  2. Per-origin SW.Ship a second SW under the CDN origin if it's yours to control. Coordinate cache version names so a deploy invalidates both.

Caveats

  • SW updates are async — there's always a one-visit lag before users see new bytes.
  • iOS Safari aggressively kills idle SWs; expect occasional cold starts.
  • Setting autoActivate: true while users have unsaved form data is a footgun.
  • The SW must be served with Content-Type: text/javascript; some static hosts default to application/javascript which works but throws on older browsers.