cal-me-j / jvoex labs

A PWA that tells users it updated — instead of quietly hoping they notice.

React 18 + Vite + TypeScript starter with a pre-configured service worker and a real update-available prompt. Static build, zero ongoing hosting cost.

A fresh app update is available! Update Now

The part every PWA starter gets wrong

Most PWA setups either update silently mid-session (confusing — the user's looking at code that changed under them) or never prompt at all (stale — they're stuck on an old build until they manually clear cache).

Silent updates break the user's mental model A service worker swaps in new code while someone's mid-interaction, and now what they're looking at doesn't match what's running. No warning, no explanation.
// ReloadPrompt.tsx — detects a waiting service worker
const {
  needRefresh: [needRefresh, setNeedRefresh],
  updateServiceWorker,
} = useRegisterSW({
  onRegistered(r) { console.log('Service worker registered', r); },
  onRegisterError(error) { console.error('Registration failed', error); },
});

// renders nothing until an update is actually waiting
if (!needRefresh) return null;
The user decides when to reload, not the app "Update Now" reloads onto the new build immediately. "Dismiss" clears the prompt without forcing a reload — no interruption to whatever they were doing.
onClick={() => updateServiceWorker(true)}>Update Now
onClick={() => setNeedRefresh(false)}>Dismiss
stale build → banner shown confirmed update → clean reload

What's in the box

Everything below ships in the single package — no tiers, one price.

service worker

vite-plugin-pwa, pre-configured

Auto-update registration set up out of the box — no manifest or workbox config to write yourself.

component

ReloadPrompt.tsx

The update-available banner shown above. Self-contained, no external state library required.

roadmap

Icon generation script — coming soon

Planned for a future update: one master .svg in, every favicon / apple-touch / maskable icon size out. Not included in this release — see the notice below for a quick manual workaround.

example

PaymentCheckoutUI.tsx

A working sample component calling a backend payment endpoint (M-Pesa / Paystack) — a real starting point to adapt, not a themeable production component.

Known limitations PaymentCheckoutUI.tsx requires a tenantId prop — it's not inferred or defaulted. Always pass your real tenant identifier; see SETUP.md for detail.

The icon generation script isn't included in this release. In the meantime, generate your icon set with a free tool like realfavicongenerator.net and drop the output into public/.