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
process.env.NODE_ENV === 'production'.Generate
jorvel sw generate
# discovers the host app → writes <host>/public/jorvel-sw.js
jorvel sw generate --app shell # or target an app explicitlyWith 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
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 class | Strategy | Rationale |
|---|---|---|
remoteEntry.js + /jorvel/remotes/** | stale-while-revalidate | Cache-first for instant load; revalidate in background so the next visit gets new code |
Fingerprinted chunks *.[hash].js | cache-first | Content-addressable — never changes for a given URL |
| HTML documents | network-first with offline fallback | Always try fresh; serve cached /offline.html if offline |
API requests (/api/**) | not cached | The SW gets out of the way for dynamic data |
Update flow
- User visits — the browser fetches
/jorvel-sw.jsin the background. - If the bytes changed, the new SW installs in parallel and waits in
installedstate. - The runtime fires
onUpdateReady. Show a banner: "A new version is available. Reload?" - 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
| Option | Default | Purpose |
|---|---|---|
url | — | Required. Path to the SW file. |
scope | same as url directory | Restricts which URLs the SW controls. |
autoActivate | false | Skip the waiting state; new SW activates immediately. |
onUpdateReady | — | Called when a new SW is installed and waiting. |
onActivated | — | Called when the new SW takes control. |
onError | — | Registration / install errors. |
Unregister
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
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:
- 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. - 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: truewhile users have unsaved form data is a footgun. - The SW must be served with
Content-Type: text/javascript; some static hosts default toapplication/javascriptwhich works but throws on older browsers.
