Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Implement a "build hash" opt-in refresh system for this Vite + React app, so users on a stale tab get notified when a new deploy has shipped and can choose to reload — without losing unsaved work.
- 1. Vite plugin (vite.config.ts)
- Add a build-time hash of all source files:
- Write a generateBuildHash() function that recursively walks src/ and public/ (skip node_modules and dist), hashing (SHA-256) the file path + contents of every .ts/.tsx/.js/.jsx/.css/.html/.json/.md file, plus the contents of package.json (so dependency changes count too). Sort directory entries before hashing for determinism. Truncate the digest to the first 16 hex chars.
- Add a custom Vite plugin with a generateBundle() hook that calls generateBundle → this.emitFile({ type: "asset", fileName: "build-hash.txt", source: buildHash }), so the hash is emitted as a plain-text static asset at /build-hash.txt in the production build.
- Also add define: { __BUILD_HASH__: JSON.stringify(generateBuildHash()) } so the same hash is inlined as a global constant baked directly into the JS bundle at build time.
- Declare the global for TypeScript in vite-env.d.ts: declare const __BUILD_HASH__: string;
- Both values come from the same hash computed at build time, so they always match for a given deploy and diverge only when the deploy changes.
- 2. React context (UpdateContext.tsx + types)
- Create an UpdateProvider that:
- Captures const currentBuildHash = __BUILD_HASH__ — the hash the currently-running bundle was built with.
- Exposes a checkForUpdates() async function that does fetch("/build-hash.txt?t=" + Date.now(), { cache: "no-cache", headers: { "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0" } }) — the query-string cache-buster plus explicit no-cache headers are required so this always hits the live server, never a stale CDN/browser cache. Compare the fetched text (trimmed) against currentBuildHash; if different, set hasUpdate = true.
- Runs this check: once ~5s after mount, then on a repeating interval (config value checkIntervalMinutes, default 15), and again whenever the browser regains network connectivity.
- Exposes hasUnsavedData / setHasUnsavedData state (other parts of the app set this when a form/edit is in progress) and a confirmUnsavedOnRefresh flag.
- Exposes refreshApp(): if hasUnsavedData && confirmUnsavedOnRefresh, show a native window.confirm(...) warning before reloading; otherwise call window.location.reload() directly.
- Exposes dismissUpdate() to clear hasUpdate without reloading.
- Add a beforeunload handler that warns on tab close/navigation when there's unsaved data and the confirm flag is set.
- Keep the context object and the useUpdate() hook in separate files from the provider component, so Vite's React Fast Refresh doesn't break (a file that exports both a component and non-component values loses fast-refresh support).
- 3. Notification UI (UpdateNotification.tsx)
- A small dismissible toast, fixed-position bottom-right, that renders only when hasUpdate is true. Show "Update Available" with a short explanatory line (different wording if there's unsaved data), a primary "Refresh Now" (or "Refresh Anyway" if unsaved data exists) button calling refreshApp(), and secondary "Later" / dismiss (X) buttons calling dismissUpdate().
- Key design point: detecting a new deploy never force-reloads the page. It only flips a flag; the actual reload is a deliberate user click, gated by an unsaved-changes confirmation. This avoids yanking a form out from under someone mid-edit while still keeping users off stale bundles for long.
Advertisement