Sometimes, you want to make sure you show a UI element to let the user know a new version of the front-app of your application is available.
Prompting the user to reload the page to get the new front-end bundle is important, as your app's back-end might have changed to something that isn't compatible with an old version of the front-end.
Detecting changes
To detect an update, the usual pattern is to do a HTTP fetch every 30 seconds or so, and see if something has changed.
There's a few ways to do this:
Compare file contents
My app is built using Vite, which will automatically create a new unique name of the JS bundle served for each build:
<script type="module" crossorigin src="/assets/index-DOay74m5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-gBd2_900.css">
So a script could fetch this index.html and compare the paths agains the last version:
const fetchVersionFingerprint = async (): Promise<string | null> => {
const res = await fetch(`/index.html?_=${Date.now()}`, { cache: "no-store" });
if (!res.ok) return null;
const assets = (await res.text()).match(/\/assets\/[^"']+\.(?:js|css)/g);
return assets ? [...new Set(assets)].sort().join(",") : null;
};
Compare HTTP headers
A more universal method might be checking the ETag header - if you have a static file being served for index.html (e.g. through AWS S3), the ETag will have a hash based on the contents of the file, so it will only change once the file has changed.
const fetchVersionTag = async (): Promise<string | null> => {
const res = await fetch(`/index.html?_=${Date.now()}`, {
method: "HEAD",
cache: "no-store"
});
return res.ok ? res.headers.get("etag") : null;
};
Showing a UI
I use react-hot-toast, one of my favourite React UI packages, to display a custom toast:
const promptForUpdate = () => {
toast(
() => (
<div className="flex items-center gap-3">
A new version of Examplary is available.
<Button onClick={() => window.location.reload()}>
Refresh
</Button>
</div>
),
{ duration: Infinity } // keep it on the screen
);
};
And then a simple React component that deals with regularly checking, which I can add into my main <App> component:
const POLL_INTERVAL_MS = 30_000;
export const VersionUpdateChecker = () => {
const { t } = useTranslation();
const baseline = useRef<string | null>(null);
useEffect(() => {
let interval;
const check = async () => {
const tag = await fetchVersionTag();
if (!tag) return;
if (baseline.current === null) {
// First time: save the baseline
baseline.current = tag;
} else if (tag !== baseline.current) {
// On change detected: show prompt
promptForUpdate(t);
clearInterval(interval);
}
};
interval = setInterval(check, POLL_INTERVAL_MS);
check();
return () => {
clearInterval(interval);
};
}, []);
return null;
};