Symptoms
- the user deployed a new version but some users still see the old UI
- the "version outdated" popup appears repeatedly on the same page
- pressing refresh still gets the old version
config.jsonupdated but the browser still reads the old value
Root Cause
vite.config.ts:chunkFileNamesno[hash]→ browser caches the old JS chunk after deployVersionChecker.tsx:emptyCache()is sync →window.location.reload()fires before the cache is actually clearedVersionChecker.tsx: no guard →getData()is called repeatedly, duplicate popups, repeated reloadsVersionChecker.tsx:fetchuses browser cache → readsconfig.jsonold
Fix: 2 files
Step 1: Edit vite.config.ts
Add [hash] in chunkFileNames — most important, forces the browser to load new JS every time you deploy
output: {
entryFileNames: `assets/js/[name].[hash].js`,
assetFileNames: `assets/[ext]/[name].[hash].[ext]`,
chunkFileNames: `assets/js/[name].[hash].js`, // ก่อนหน้านี้ไม่มี [hash]
},
Step 2: Edit src/app/layout/components/VersionChecker.tsx
Replace the entire file with the fixed version:
import React, { useEffect, useRef } from "react";
import { APP_INFO, VERSION_CHECKER } from "../../../Const";
import { swalConfirm } from "../../modules/_common";
function VersionChecker() {
const { version } = APP_INFO;
const { CHECK_VERSION_EVERY_MINUTE, CONFIRM_BEFORE_REFRESH, ENABLE_VERSION_CHECKER } = VERSION_CHECKER;
const isCheckingRef = useRef(false);
const isPromptOpenRef = useRef(false);
const isReloadingRef = useRef(false);
const checkVersionLoop = 1000 * 60 * CHECK_VERSION_EVERY_MINUTE;
const getData = async () => {
// guard: ป้องกันเรียกซ้อน
if (!ENABLE_VERSION_CHECKER || isCheckingRef.current || isReloadingRef.current) {
return;
}
isCheckingRef.current = true;
try {
const response = await fetch(`${window.location.origin}/config.json`, {
cache: "no-store", // บังคับไม่ใช้ browser cache
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`Failed to fetch config.json: ${response.status}`);
}
const configData = await response.json();
if (configData.version !== version) {
if (CONFIRM_BEFORE_REFRESH) {
if (isPromptOpenRef.current) {
return; // ป้องกัน popup ซ้ำ
}
isPromptOpenRef.current = true;
try {
const res = await swalConfirm("Warning", "Your version is outdated. Refresh now?");
if (res.isConfirmed) {
await emptyCache();
}
} finally {
isPromptOpenRef.current = false;
}
} else {
await emptyCache();
}
}
} catch (error) {
console.error("Version check failed", error); // ไม่ใช้ alert()
} finally {
isCheckingRef.current = false;
}
};
const emptyCache = async () => {
if (isReloadingRef.current) {
return; // ป้องกัน reload ซ้ำ
}
isReloadingRef.current = true;
try {
if ("caches" in window) {
const names = await caches.keys();
// await ให้ลบ cache เสร็จก่อนค่อย reload
await Promise.all(names.map((name) => caches.delete(name)));
}
} finally {
window.location.reload();
}
};
useEffect(() => {
void getData();
const interval = setInterval(() => {
void getData();
}, checkVersionLoop);
return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <></>;
}
export default VersionChecker;
What changed and why
| Changed point | Before | After | Reason |
|---|---|---|---|
chunkFileNames |
[name].js |
[name].[hash].js |
Forces the browser to load new JS after deploy |
emptyCache |
sync, forEach | async, Promise.all + await |
Guarantees the cache is cleared before reload |
| guard refs | None | isCheckingRef, isPromptOpenRef, isReloadingRef |
Prevents race condition |
| fetch cache | browser default | cache: "no-store" |
Prevents reading old config.json |
| error handling | alert() |
console.error() |
Does not disturb the user when the network temporarily fails |
| prompt text | "your version is out-dated..." |
"Your version is outdated. Refresh now?" |
More readable language |
Step 3: Verify
# 1. Build ต้องผ่าน
npm run build
# 2. ตรวจว่า chunk files มี hash ใน dist/
ls dist/assets/js/
# ควรเห็น: index.abc123.js, vendor.def456.js (มี hash ทุกไฟล์)
# 3. ทดสอบ flow
# - เปิด DevTools → Network tab → เปิด "Disable cache"
# - Reload หน้า → ตรวจว่า config.json fetch มี Cache-Control: no-store
# - เปลี่ยน version ใน config.json → รอ interval หรือ reload → popup ต้องขึ้น 1 ครั้งเท่านั้น
# - กด refresh → หน้าต้องโหลด version ใหม่