From bf0754d21e44e8645930b87728e5355248c3c081 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 23 May 2026 20:31:42 +0200 Subject: [PATCH] fix(frontend): reload page on Vite chunk preload error after upgrade After a panel upgrade the embedded dist/ ships with new hashed chunk filenames, so SPA tabs loaded before the upgrade hold references to chunks that no longer exist on the server and lazy modals 404. Hook `vite:preloadError` and force one full reload (guarded by a session flag) so the browser picks up the new index.html. --- frontend/src/entries/api-docs.tsx | 1 + frontend/src/entries/clients.tsx | 1 + frontend/src/entries/inbounds.tsx | 1 + frontend/src/entries/index.tsx | 1 + frontend/src/entries/login.tsx | 1 + frontend/src/entries/nodes.tsx | 1 + frontend/src/entries/settings.tsx | 1 + frontend/src/entries/subpage.tsx | 1 + frontend/src/entries/xray.tsx | 1 + frontend/src/utils/chunkReload.ts | 17 +++++++++++++++++ 10 files changed, 26 insertions(+) create mode 100644 frontend/src/utils/chunkReload.ts diff --git a/frontend/src/entries/api-docs.tsx b/frontend/src/entries/api-docs.tsx index 9a32c5cd..f84b7e95 100644 --- a/frontend/src/entries/api-docs.tsx +++ b/frontend/src/entries/api-docs.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/clients.tsx b/frontend/src/entries/clients.tsx index a6834e3f..ff654119 100644 --- a/frontend/src/entries/clients.tsx +++ b/frontend/src/entries/clients.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/inbounds.tsx b/frontend/src/entries/inbounds.tsx index f59a16e9..841b643d 100644 --- a/frontend/src/entries/inbounds.tsx +++ b/frontend/src/entries/inbounds.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/index.tsx b/frontend/src/entries/index.tsx index c3620cae..e57f3be3 100644 --- a/frontend/src/entries/index.tsx +++ b/frontend/src/entries/index.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/login.tsx b/frontend/src/entries/login.tsx index 66fc4f1a..75c933fe 100644 --- a/frontend/src/entries/login.tsx +++ b/frontend/src/entries/login.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/nodes.tsx b/frontend/src/entries/nodes.tsx index 75761eba..c639ef34 100644 --- a/frontend/src/entries/nodes.tsx +++ b/frontend/src/entries/nodes.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/settings.tsx b/frontend/src/entries/settings.tsx index b6397963..0ee78267 100644 --- a/frontend/src/entries/settings.tsx +++ b/frontend/src/entries/settings.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/entries/subpage.tsx b/frontend/src/entries/subpage.tsx index fbe6ea75..b4dbaab7 100644 --- a/frontend/src/entries/subpage.tsx +++ b/frontend/src/entries/subpage.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { readyI18n } from '@/i18n/react'; import { ThemeProvider } from '@/hooks/useTheme'; import SubPage from '@/pages/sub/SubPage'; diff --git a/frontend/src/entries/xray.tsx b/frontend/src/entries/xray.tsx index 3b579254..c9f92f17 100644 --- a/frontend/src/entries/xray.tsx +++ b/frontend/src/entries/xray.tsx @@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client'; import { message } from 'antd'; import 'antd/dist/reset.css'; +import '@/utils/chunkReload'; import { setupAxios } from '@/api/axios-init.js'; import { applyDocumentTitle } from '@/utils'; import { readyI18n } from '@/i18n/react'; diff --git a/frontend/src/utils/chunkReload.ts b/frontend/src/utils/chunkReload.ts new file mode 100644 index 00000000..1f6d6fc0 --- /dev/null +++ b/frontend/src/utils/chunkReload.ts @@ -0,0 +1,17 @@ +// After a panel upgrade the embedded dist/ ships with new hashed chunk +// filenames, so an SPA that was loaded before the upgrade still holds +// references to chunks that no longer exist on the server. The first +// time a lazy import 404s we force a full reload so the browser picks +// up the new index.html and its new chunk references. +if (typeof window !== 'undefined') { + const RELOAD_FLAG = '__xuiChunkReloadOnce'; + window.addEventListener('vite:preloadError', (event) => { + event.preventDefault(); + if (sessionStorage.getItem(RELOAD_FLAG) === '1') return; + sessionStorage.setItem(RELOAD_FLAG, '1'); + window.location.reload(); + }); + window.addEventListener('load', () => { + sessionStorage.removeItem(RELOAD_FLAG); + }); +}