mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
fix: auto-reload on stale chunk errors after deploy
When a new build changes chunk hashes, users with cached index.html get "Failed to fetch dynamically imported module" errors. Now: - lazyWithRetry() wraps all lazy imports with auto-reload on failure - ErrorBoundary catches chunk errors and auto-reloads once - 30s guard in sessionStorage prevents reload loops
This commit is contained in:
211
src/App.tsx
211
src/App.tsx
@@ -1,6 +1,25 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { lazy, Suspense, type ComponentType } from 'react';
|
||||
import { Routes, Route, Navigate, useLocation, useParams } from 'react-router';
|
||||
import { useAuthStore } from './store/auth';
|
||||
|
||||
/**
|
||||
* Wrapper around React.lazy that auto-reloads the page when a chunk fails to load
|
||||
* (e.g. after a new deploy with different chunk hashes).
|
||||
*/
|
||||
function lazyWithRetry<T extends ComponentType<unknown>>(factory: () => Promise<{ default: T }>) {
|
||||
return lazy(() =>
|
||||
factory().catch(() => {
|
||||
const key = 'chunk_reload_ts';
|
||||
const last = Number(sessionStorage.getItem(key) || '0');
|
||||
if (Date.now() - last > 30_000) {
|
||||
sessionStorage.setItem(key, String(Date.now()));
|
||||
window.location.reload();
|
||||
}
|
||||
// Re-throw so ErrorBoundary catches it if reload guard prevents loop
|
||||
return factory();
|
||||
}),
|
||||
);
|
||||
}
|
||||
import { useBlockingStore } from './store/blocking';
|
||||
import Layout from './components/layout/Layout';
|
||||
import PageLoader from './components/common/PageLoader';
|
||||
@@ -26,103 +45,107 @@ import OAuthCallback from './pages/OAuthCallback';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
|
||||
// User pages - lazy load
|
||||
const Subscriptions = lazy(() => import('./pages/Subscriptions'));
|
||||
const Subscription = lazy(() => import('./pages/Subscription'));
|
||||
const SubscriptionPurchase = lazy(() => import('./pages/SubscriptionPurchase'));
|
||||
const Balance = lazy(() => import('./pages/Balance'));
|
||||
const SavedCards = lazy(() => import('./pages/SavedCards'));
|
||||
const Referral = lazy(() => import('./pages/Referral'));
|
||||
const Support = lazy(() => import('./pages/Support'));
|
||||
const Profile = lazy(() => import('./pages/Profile'));
|
||||
const Contests = lazy(() => import('./pages/Contests'));
|
||||
const Polls = lazy(() => import('./pages/Polls'));
|
||||
const Info = lazy(() => import('./pages/Info'));
|
||||
const Wheel = lazy(() => import('./pages/Wheel'));
|
||||
const GiftSubscription = lazy(() => import('./pages/GiftSubscription'));
|
||||
const GiftResult = lazy(() => import('./pages/GiftResult'));
|
||||
const Connection = lazy(() => import('./pages/Connection'));
|
||||
const ConnectionQR = lazy(() => import('./pages/ConnectionQR'));
|
||||
const QuickPurchase = lazy(() => import('./pages/QuickPurchase'));
|
||||
const PurchaseSuccess = lazy(() => import('./pages/PurchaseSuccess'));
|
||||
const RenewSubscription = lazy(() => import('./pages/RenewSubscription'));
|
||||
const AutoLogin = lazy(() => import('./pages/AutoLogin'));
|
||||
const TopUpMethodSelect = lazy(() => import('./pages/TopUpMethodSelect'));
|
||||
const TopUpAmount = lazy(() => import('./pages/TopUpAmount'));
|
||||
const TopUpResult = lazy(() => import('./pages/TopUpResult'));
|
||||
const ConnectedAccounts = lazy(() => import('./pages/ConnectedAccounts'));
|
||||
const LinkTelegramCallback = lazy(() => import('./pages/LinkTelegramCallback'));
|
||||
const MergeAccounts = lazy(() => import('./pages/MergeAccounts'));
|
||||
const Subscriptions = lazyWithRetry(() => import('./pages/Subscriptions'));
|
||||
const Subscription = lazyWithRetry(() => import('./pages/Subscription'));
|
||||
const SubscriptionPurchase = lazyWithRetry(() => import('./pages/SubscriptionPurchase'));
|
||||
const Balance = lazyWithRetry(() => import('./pages/Balance'));
|
||||
const SavedCards = lazyWithRetry(() => import('./pages/SavedCards'));
|
||||
const Referral = lazyWithRetry(() => import('./pages/Referral'));
|
||||
const Support = lazyWithRetry(() => import('./pages/Support'));
|
||||
const Profile = lazyWithRetry(() => import('./pages/Profile'));
|
||||
const Contests = lazyWithRetry(() => import('./pages/Contests'));
|
||||
const Polls = lazyWithRetry(() => import('./pages/Polls'));
|
||||
const Info = lazyWithRetry(() => import('./pages/Info'));
|
||||
const Wheel = lazyWithRetry(() => import('./pages/Wheel'));
|
||||
const GiftSubscription = lazyWithRetry(() => import('./pages/GiftSubscription'));
|
||||
const GiftResult = lazyWithRetry(() => import('./pages/GiftResult'));
|
||||
const Connection = lazyWithRetry(() => import('./pages/Connection'));
|
||||
const ConnectionQR = lazyWithRetry(() => import('./pages/ConnectionQR'));
|
||||
const QuickPurchase = lazyWithRetry(() => import('./pages/QuickPurchase'));
|
||||
const PurchaseSuccess = lazyWithRetry(() => import('./pages/PurchaseSuccess'));
|
||||
const RenewSubscription = lazyWithRetry(() => import('./pages/RenewSubscription'));
|
||||
const AutoLogin = lazyWithRetry(() => import('./pages/AutoLogin'));
|
||||
const TopUpMethodSelect = lazyWithRetry(() => import('./pages/TopUpMethodSelect'));
|
||||
const TopUpAmount = lazyWithRetry(() => import('./pages/TopUpAmount'));
|
||||
const TopUpResult = lazyWithRetry(() => import('./pages/TopUpResult'));
|
||||
const ConnectedAccounts = lazyWithRetry(() => import('./pages/ConnectedAccounts'));
|
||||
const LinkTelegramCallback = lazyWithRetry(() => import('./pages/LinkTelegramCallback'));
|
||||
const MergeAccounts = lazyWithRetry(() => import('./pages/MergeAccounts'));
|
||||
|
||||
// Admin pages - lazy load (only for admins)
|
||||
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
|
||||
const AdminTickets = lazy(() => import('./pages/AdminTickets'));
|
||||
const AdminTicketSettings = lazy(() => import('./pages/AdminTicketSettings'));
|
||||
const AdminSettings = lazy(() => import('./pages/AdminSettings'));
|
||||
const AdminApps = lazy(() => import('./pages/AdminApps'));
|
||||
const AdminWheel = lazy(() => import('./pages/AdminWheel'));
|
||||
const AdminTariffs = lazy(() => import('./pages/AdminTariffs'));
|
||||
const AdminTariffCreate = lazy(() => import('./pages/AdminTariffCreate'));
|
||||
const AdminServers = lazy(() => import('./pages/AdminServers'));
|
||||
const AdminServerEdit = lazy(() => import('./pages/AdminServerEdit'));
|
||||
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
|
||||
const AdminBanSystem = lazy(() => import('./pages/AdminBanSystem'));
|
||||
const AdminBroadcasts = lazy(() => import('./pages/AdminBroadcasts'));
|
||||
const AdminBroadcastCreate = lazy(() => import('./pages/AdminBroadcastCreate'));
|
||||
const AdminPromocodes = lazy(() => import('./pages/AdminPromocodes'));
|
||||
const AdminPromocodeCreate = lazy(() => import('./pages/AdminPromocodeCreate'));
|
||||
const AdminPromocodeStats = lazy(() => import('./pages/AdminPromocodeStats'));
|
||||
const AdminPromoGroups = lazy(() => import('./pages/AdminPromoGroups'));
|
||||
const AdminPromoGroupCreate = lazy(() => import('./pages/AdminPromoGroupCreate'));
|
||||
const AdminCampaigns = lazy(() => import('./pages/AdminCampaigns'));
|
||||
const AdminCampaignCreate = lazy(() => import('./pages/AdminCampaignCreate'));
|
||||
const AdminCampaignStats = lazy(() => import('./pages/AdminCampaignStats'));
|
||||
const AdminCampaignEdit = lazy(() => import('./pages/AdminCampaignEdit'));
|
||||
const AdminPartners = lazy(() => import('./pages/AdminPartners'));
|
||||
const AdminPartnerSettings = lazy(() => import('./pages/AdminPartnerSettings'));
|
||||
const AdminPartnerDetail = lazy(() => import('./pages/AdminPartnerDetail'));
|
||||
const AdminApplicationReview = lazy(() => import('./pages/AdminApplicationReview'));
|
||||
const AdminPartnerCommission = lazy(() => import('./pages/AdminPartnerCommission'));
|
||||
const AdminPartnerRevoke = lazy(() => import('./pages/AdminPartnerRevoke'));
|
||||
const AdminPartnerCampaignAssign = lazy(() => import('./pages/AdminPartnerCampaignAssign'));
|
||||
const AdminWithdrawals = lazy(() => import('./pages/AdminWithdrawals'));
|
||||
const AdminWithdrawalDetail = lazy(() => import('./pages/AdminWithdrawalDetail'));
|
||||
const AdminWithdrawalReject = lazy(() => import('./pages/AdminWithdrawalReject'));
|
||||
const ReferralPartnerApply = lazy(() => import('./pages/ReferralPartnerApply'));
|
||||
const ReferralWithdrawalRequest = lazy(() => import('./pages/ReferralWithdrawalRequest'));
|
||||
const AdminUsers = lazy(() => import('./pages/AdminUsers'));
|
||||
const AdminPayments = lazy(() => import('./pages/AdminPayments'));
|
||||
const AdminPaymentMethods = lazy(() => import('./pages/AdminPaymentMethods'));
|
||||
const AdminPaymentMethodEdit = lazy(() => import('./pages/AdminPaymentMethodEdit'));
|
||||
const AdminPromoOffers = lazy(() => import('./pages/AdminPromoOffers'));
|
||||
const AdminPromoOfferTemplateEdit = lazy(() => import('./pages/AdminPromoOfferTemplateEdit'));
|
||||
const AdminPromoOfferSend = lazy(() => import('./pages/AdminPromoOfferSend'));
|
||||
const AdminRemnawave = lazy(() => import('./pages/AdminRemnawave'));
|
||||
const AdminRemnawaveSquadDetail = lazy(() => import('./pages/AdminRemnawaveSquadDetail'));
|
||||
const AdminEmailTemplates = lazy(() => import('./pages/AdminEmailTemplates'));
|
||||
const AdminTrafficUsage = lazy(() => import('./pages/AdminTrafficUsage'));
|
||||
const AdminSalesStats = lazy(() => import('./pages/AdminSalesStats'));
|
||||
const AdminUpdates = lazy(() => import('./pages/AdminUpdates'));
|
||||
const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail'));
|
||||
const AdminBroadcastDetail = lazy(() => import('./pages/AdminBroadcastDetail'));
|
||||
const AdminPinnedMessages = lazy(() => import('./pages/AdminPinnedMessages'));
|
||||
const AdminPinnedMessageCreate = lazy(() => import('./pages/AdminPinnedMessageCreate'));
|
||||
const AdminChannelSubscriptions = lazy(() => import('./pages/AdminChannelSubscriptions'));
|
||||
const AdminEmailTemplatePreview = lazy(() => import('./pages/AdminEmailTemplatePreview'));
|
||||
const AdminRoles = lazy(() => import('./pages/AdminRoles'));
|
||||
const AdminRoleEdit = lazy(() => import('./pages/AdminRoleEdit'));
|
||||
const AdminRoleAssign = lazy(() => import('./pages/AdminRoleAssign'));
|
||||
const AdminPolicies = lazy(() => import('./pages/AdminPolicies'));
|
||||
const AdminPolicyEdit = lazy(() => import('./pages/AdminPolicyEdit'));
|
||||
const AdminAuditLog = lazy(() => import('./pages/AdminAuditLog'));
|
||||
const AdminLandings = lazy(() => import('./pages/AdminLandings'));
|
||||
const AdminLandingEditor = lazy(() => import('./pages/AdminLandingEditor'));
|
||||
const AdminLandingStats = lazy(() => import('./pages/AdminLandingStats'));
|
||||
const AdminReferralNetwork = lazy(() => import('./pages/ReferralNetwork'));
|
||||
const AdminPanel = lazyWithRetry(() => import('./pages/AdminPanel'));
|
||||
const AdminTickets = lazyWithRetry(() => import('./pages/AdminTickets'));
|
||||
const AdminTicketSettings = lazyWithRetry(() => import('./pages/AdminTicketSettings'));
|
||||
const AdminSettings = lazyWithRetry(() => import('./pages/AdminSettings'));
|
||||
const AdminApps = lazyWithRetry(() => import('./pages/AdminApps'));
|
||||
const AdminWheel = lazyWithRetry(() => import('./pages/AdminWheel'));
|
||||
const AdminTariffs = lazyWithRetry(() => import('./pages/AdminTariffs'));
|
||||
const AdminTariffCreate = lazyWithRetry(() => import('./pages/AdminTariffCreate'));
|
||||
const AdminServers = lazyWithRetry(() => import('./pages/AdminServers'));
|
||||
const AdminServerEdit = lazyWithRetry(() => import('./pages/AdminServerEdit'));
|
||||
const AdminDashboard = lazyWithRetry(() => import('./pages/AdminDashboard'));
|
||||
const AdminBanSystem = lazyWithRetry(() => import('./pages/AdminBanSystem'));
|
||||
const AdminBroadcasts = lazyWithRetry(() => import('./pages/AdminBroadcasts'));
|
||||
const AdminBroadcastCreate = lazyWithRetry(() => import('./pages/AdminBroadcastCreate'));
|
||||
const AdminPromocodes = lazyWithRetry(() => import('./pages/AdminPromocodes'));
|
||||
const AdminPromocodeCreate = lazyWithRetry(() => import('./pages/AdminPromocodeCreate'));
|
||||
const AdminPromocodeStats = lazyWithRetry(() => import('./pages/AdminPromocodeStats'));
|
||||
const AdminPromoGroups = lazyWithRetry(() => import('./pages/AdminPromoGroups'));
|
||||
const AdminPromoGroupCreate = lazyWithRetry(() => import('./pages/AdminPromoGroupCreate'));
|
||||
const AdminCampaigns = lazyWithRetry(() => import('./pages/AdminCampaigns'));
|
||||
const AdminCampaignCreate = lazyWithRetry(() => import('./pages/AdminCampaignCreate'));
|
||||
const AdminCampaignStats = lazyWithRetry(() => import('./pages/AdminCampaignStats'));
|
||||
const AdminCampaignEdit = lazyWithRetry(() => import('./pages/AdminCampaignEdit'));
|
||||
const AdminPartners = lazyWithRetry(() => import('./pages/AdminPartners'));
|
||||
const AdminPartnerSettings = lazyWithRetry(() => import('./pages/AdminPartnerSettings'));
|
||||
const AdminPartnerDetail = lazyWithRetry(() => import('./pages/AdminPartnerDetail'));
|
||||
const AdminApplicationReview = lazyWithRetry(() => import('./pages/AdminApplicationReview'));
|
||||
const AdminPartnerCommission = lazyWithRetry(() => import('./pages/AdminPartnerCommission'));
|
||||
const AdminPartnerRevoke = lazyWithRetry(() => import('./pages/AdminPartnerRevoke'));
|
||||
const AdminPartnerCampaignAssign = lazyWithRetry(
|
||||
() => import('./pages/AdminPartnerCampaignAssign'),
|
||||
);
|
||||
const AdminWithdrawals = lazyWithRetry(() => import('./pages/AdminWithdrawals'));
|
||||
const AdminWithdrawalDetail = lazyWithRetry(() => import('./pages/AdminWithdrawalDetail'));
|
||||
const AdminWithdrawalReject = lazyWithRetry(() => import('./pages/AdminWithdrawalReject'));
|
||||
const ReferralPartnerApply = lazyWithRetry(() => import('./pages/ReferralPartnerApply'));
|
||||
const ReferralWithdrawalRequest = lazyWithRetry(() => import('./pages/ReferralWithdrawalRequest'));
|
||||
const AdminUsers = lazyWithRetry(() => import('./pages/AdminUsers'));
|
||||
const AdminPayments = lazyWithRetry(() => import('./pages/AdminPayments'));
|
||||
const AdminPaymentMethods = lazyWithRetry(() => import('./pages/AdminPaymentMethods'));
|
||||
const AdminPaymentMethodEdit = lazyWithRetry(() => import('./pages/AdminPaymentMethodEdit'));
|
||||
const AdminPromoOffers = lazyWithRetry(() => import('./pages/AdminPromoOffers'));
|
||||
const AdminPromoOfferTemplateEdit = lazyWithRetry(
|
||||
() => import('./pages/AdminPromoOfferTemplateEdit'),
|
||||
);
|
||||
const AdminPromoOfferSend = lazyWithRetry(() => import('./pages/AdminPromoOfferSend'));
|
||||
const AdminRemnawave = lazyWithRetry(() => import('./pages/AdminRemnawave'));
|
||||
const AdminRemnawaveSquadDetail = lazyWithRetry(() => import('./pages/AdminRemnawaveSquadDetail'));
|
||||
const AdminEmailTemplates = lazyWithRetry(() => import('./pages/AdminEmailTemplates'));
|
||||
const AdminTrafficUsage = lazyWithRetry(() => import('./pages/AdminTrafficUsage'));
|
||||
const AdminSalesStats = lazyWithRetry(() => import('./pages/AdminSalesStats'));
|
||||
const AdminUpdates = lazyWithRetry(() => import('./pages/AdminUpdates'));
|
||||
const AdminUserDetail = lazyWithRetry(() => import('./pages/AdminUserDetail'));
|
||||
const AdminBroadcastDetail = lazyWithRetry(() => import('./pages/AdminBroadcastDetail'));
|
||||
const AdminPinnedMessages = lazyWithRetry(() => import('./pages/AdminPinnedMessages'));
|
||||
const AdminPinnedMessageCreate = lazyWithRetry(() => import('./pages/AdminPinnedMessageCreate'));
|
||||
const AdminChannelSubscriptions = lazyWithRetry(() => import('./pages/AdminChannelSubscriptions'));
|
||||
const AdminEmailTemplatePreview = lazyWithRetry(() => import('./pages/AdminEmailTemplatePreview'));
|
||||
const AdminRoles = lazyWithRetry(() => import('./pages/AdminRoles'));
|
||||
const AdminRoleEdit = lazyWithRetry(() => import('./pages/AdminRoleEdit'));
|
||||
const AdminRoleAssign = lazyWithRetry(() => import('./pages/AdminRoleAssign'));
|
||||
const AdminPolicies = lazyWithRetry(() => import('./pages/AdminPolicies'));
|
||||
const AdminPolicyEdit = lazyWithRetry(() => import('./pages/AdminPolicyEdit'));
|
||||
const AdminAuditLog = lazyWithRetry(() => import('./pages/AdminAuditLog'));
|
||||
const AdminLandings = lazyWithRetry(() => import('./pages/AdminLandings'));
|
||||
const AdminLandingEditor = lazyWithRetry(() => import('./pages/AdminLandingEditor'));
|
||||
const AdminLandingStats = lazyWithRetry(() => import('./pages/AdminLandingStats'));
|
||||
const AdminReferralNetwork = lazyWithRetry(() => import('./pages/ReferralNetwork'));
|
||||
|
||||
// News pages
|
||||
const NewsArticlePage = lazy(() => import('./pages/NewsArticle'));
|
||||
const AdminNews = lazy(() => import('./pages/AdminNews'));
|
||||
const AdminNewsCreate = lazy(() => import('./pages/AdminNewsCreate'));
|
||||
const NewsArticlePage = lazyWithRetry(() => import('./pages/NewsArticle'));
|
||||
const AdminNews = lazyWithRetry(() => import('./pages/AdminNews'));
|
||||
const AdminNewsCreate = lazyWithRetry(() => import('./pages/AdminNewsCreate'));
|
||||
|
||||
function ProtectedRoute({
|
||||
children,
|
||||
|
||||
@@ -11,6 +11,18 @@ interface ErrorBoundaryState {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
function isChunkLoadError(error: Error): boolean {
|
||||
const msg = error.message || '';
|
||||
return (
|
||||
msg.includes('dynamically imported module') ||
|
||||
msg.includes('Importing a module script failed') ||
|
||||
msg.includes('Failed to fetch dynamically imported module') ||
|
||||
msg.includes('Loading chunk') ||
|
||||
msg.includes('ChunkLoadError') ||
|
||||
error.name === 'ChunkLoadError'
|
||||
);
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
@@ -23,6 +35,19 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error('[ErrorBoundary]', error, errorInfo);
|
||||
|
||||
// Auto-reload on chunk load failures (stale deploy)
|
||||
if (isChunkLoadError(error)) {
|
||||
const reloadKey = 'chunk_reload_ts';
|
||||
const lastReload = Number(sessionStorage.getItem(reloadKey) || '0');
|
||||
const now = Date.now();
|
||||
// Prevent reload loop — only auto-reload once per 30 seconds
|
||||
if (now - lastReload > 30_000) {
|
||||
sessionStorage.setItem(reloadKey, String(now));
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
@@ -36,6 +61,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
||||
}
|
||||
|
||||
const { level = 'page' } = this.props;
|
||||
const isChunk = this.state.error ? isChunkLoadError(this.state.error) : false;
|
||||
|
||||
if (level === 'app') {
|
||||
return (
|
||||
@@ -78,13 +104,15 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
||||
<div className="mb-4 text-4xl">⚠️</div>
|
||||
<h1 className="mb-2 text-xl font-bold text-dark-50">Something went wrong</h1>
|
||||
<p className="mb-6 text-sm text-dark-400">
|
||||
{this.state.error?.message || 'An unexpected error occurred'}
|
||||
{isChunk
|
||||
? 'App was updated. Reloading...'
|
||||
: this.state.error?.message || 'An unexpected error occurred'}
|
||||
</p>
|
||||
<button
|
||||
onClick={this.handleReset}
|
||||
onClick={() => window.location.reload()}
|
||||
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-white transition-colors hover:bg-accent-600"
|
||||
>
|
||||
Try again
|
||||
{isChunk ? 'Reload' : 'Try again'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user