@1moby/react-sw-updater
v1.0.0
Published
Lightweight React library for detecting app updates via version polling — with cache-busting, offline awareness, chunk error recovery, and a drop-in update banner.
Maintainers
Readme
@1moby/react-sw-updater
Lightweight React library for detecting app updates via version polling — with cache-busting, offline awareness, chunk error recovery, and a drop-in update banner.
Zero runtime dependencies. ~3KB gzipped.
How It Works
- Your build tool generates a
version.jsonwith a unique build version - The library polls
version.jsonwith aggressive cache-busting (timestamp param +no-store+no-cacheheaders) - When the server version differs from the bundled version, an update prompt appears
- On accept: unregisters service workers, clears all caches, hard-reloads with a CDN-busting param
Install
npm install @1moby/react-sw-updaterQuick Start
1. Add the Vite plugin
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { updateChecker } from '@1moby/react-sw-updater/vite';
export default defineConfig({
plugins: [react(), updateChecker()],
});The plugin:
- Generates a deterministic
BUILD_VERSION(git hash + timestamp) - Defines
__BUILD_VERSION__as a global constant in your bundle - Writes
version.jsonto your output directory on build - Deduplicates React (prevents dual-instance issues)
2. Add the provider to your app
// App.tsx
import { UpdateProvider, useUpdateContext, UpdateBanner } from '@1moby/react-sw-updater';
declare const __BUILD_VERSION__: string;
function AppUpdateBanner() {
const { updateAvailable, applyUpdate, dismiss } = useUpdateContext();
if (!updateAvailable) return null;
return (
<UpdateBanner
onAccept={applyUpdate}
onDismiss={dismiss}
/>
);
}
export default function App() {
return (
<UpdateProvider currentVersion={__BUILD_VERSION__}>
<AppUpdateBanner />
{/* your app */}
</UpdateProvider>
);
}That's it. The library handles polling, cache-busting, offline detection, and reload-loop prevention automatically.
Next.js Setup
// next.config.js
const { withUpdateChecker } = require('@1moby/react-sw-updater/nextjs');
module.exports = withUpdateChecker({
// your Next.js config
});Then use process.env.NEXT_PUBLIC_BUILD_VERSION as your currentVersion.
API
<UpdateProvider>
Context provider that handles version polling and update detection.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| currentVersion | string | required | Build-time version baked into the bundle |
| versionUrl | string | '/version.json' | URL to the version endpoint |
| checkInterval | number | 300000 (5 min) | Polling interval in ms |
| swUrl | string | — | Optional SW to register for offline caching |
useUpdateContext()
Returns the update state from the nearest <UpdateProvider>.
interface UpdateCheckerResult {
updateAvailable: boolean; // true when server version differs
serverVersion: string | null; // the server's version string
applyUpdate: () => void; // clear caches + hard reload
dismiss: () => void; // hide banner until next page load
}useUpdateChecker(options)
Standalone hook (no provider needed) with the same options and return type.
<UpdateBanner>
Pre-styled, accessible banner component.
| Prop | Type | Default |
|------|------|---------|
| message | string | 'A new version is available.' |
| acceptLabel | string | 'Update' |
| dismissLabel | string | 'Later' |
| onAccept | () => void | required |
| onDismiss | () => void | — |
| className | string | — |
| style | CSSProperties | — |
Utilities
import { isChunkLoadError, retryDynamicImport } from '@1moby/react-sw-updater';
// Detect chunk load errors (code-split lazy imports failing after deploy)
isChunkLoadError(error); // boolean
// Wrap lazy imports with auto-retry on chunk errors
const MyPage = lazy(() => retryDynamicImport(() => import('./MyPage')));
// Per-tab state persistence (survives reload, isolated per tab)
import { persistState, restoreState } from '@1moby/react-sw-updater';
persistState('form-data', { name: 'John' });
const data = restoreState('form-data'); // null if not found (auto-cleans)Reliability Features
- Fetch timeout — 10s AbortController timeout prevents hanging on slow networks
- Concurrent check dedup — visibility-change + timer firing simultaneously won't cause duplicate fetches
- Offline awareness — skips polling when
navigator.onLineis false, checks immediately when connectivity returns - Reload-loop guard —
applyUpdate()blocks rapid successive reloads (30s cooldown) in case CDN hasn't purged yet - Per-route chunk retry —
retryDynamicImportuses per-pathname keys so a chunk error on/settingsdoesn't block retry on/dashboard - Recursive setTimeout — never uses
setInterval, preventing call stacking on slow networks
How applyUpdate() Works
When the user accepts the update:
- Unregisters all service workers
- Clears all Cache API caches
- Navigates with a
_uc=<timestamp>cache-bust param (cleaned up on next load)
This "nuclear" approach guarantees the browser loads fresh assets regardless of CDN, SW, or HTTP cache state.
License
MIT
