From 92ab2ddae7b5836372e02e3228ffbcb9fe1b9002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=87=AF?= <1664940968@qq.com> Date: Wed, 24 Jun 2026 17:39:28 +0800 Subject: [PATCH] fix(auth): recover from stale login chunks after logout --- web/src/app/router.tsx | 12 +++++- web/src/shared/lib/dynamic-import-recovery.ts | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 web/src/shared/lib/dynamic-import-recovery.ts diff --git a/web/src/app/router.tsx b/web/src/app/router.tsx index c9b3bac87..2ef97727a 100644 --- a/web/src/app/router.tsx +++ b/web/src/app/router.tsx @@ -4,6 +4,7 @@ import { Layout } from './layout' import { getCurrentUser } from '@/api/client' import { RoleGuard } from '@/shared/components/role-guard' import { createRequireAuth } from '@/shared/lib/auth-route' +import { clearDynamicImportReloadGuard, recoverFromDynamicImportError } from '@/shared/lib/dynamic-import-recovery' import { normalizeSearchQuery } from '@/shared/lib/search-query' /** @@ -25,7 +26,12 @@ function createLazyRouteComponent>( // Lazy route modules are wrapped in a uniform suspense fallback so route transitions behave // consistently across public and dashboard pages. const LazyComponent = lazy(async () => { - const module = await importer() + const module = await importer().catch((error) => { + if (recoverFromDynamicImportError(error)) { + return new Promise(() => {}) + } + throw error + }) return { default: module[exportName] as ComponentType> } }) @@ -477,6 +483,10 @@ export const router = createRouter({ defaultNotFoundComponent: DefaultNotFound, }) +router.subscribe('onResolved', () => { + clearDynamicImportReloadGuard() +}) + declare module '@tanstack/react-router' { interface Register { router: typeof router diff --git a/web/src/shared/lib/dynamic-import-recovery.ts b/web/src/shared/lib/dynamic-import-recovery.ts new file mode 100644 index 000000000..06d0ef221 --- /dev/null +++ b/web/src/shared/lib/dynamic-import-recovery.ts @@ -0,0 +1,38 @@ +const RELOAD_GUARD_KEY = 'skillhub:dynamic-import-reload' + +function resolveErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message + } + return String(error ?? '') +} + +export function isDynamicImportFetchError(error: unknown): boolean { + const message = resolveErrorMessage(error) + return message.includes('Failed to fetch dynamically imported module') + || message.includes('error loading dynamically imported module') + || message.includes('Importing a module script failed') + || message.includes('ChunkLoadError') +} + +export function recoverFromDynamicImportError(error: unknown): boolean { + if (typeof window === 'undefined' || !isDynamicImportFetchError(error)) { + return false + } + + if (window.sessionStorage.getItem(RELOAD_GUARD_KEY) === '1') { + window.sessionStorage.removeItem(RELOAD_GUARD_KEY) + return false + } + + window.sessionStorage.setItem(RELOAD_GUARD_KEY, '1') + window.location.reload() + return true +} + +export function clearDynamicImportReloadGuard(): void { + if (typeof window === 'undefined') { + return + } + window.sessionStorage.removeItem(RELOAD_GUARD_KEY) +}