fix(app): reload onto a new build instead of crashing on a missing chunk
A tab left open across a release asked for a lazy chunk the server no longer has (prod: DownloadDialog 404 an hour after 0.89) and fell to the crash screen. `vite:preloadError` now reloads the tab once a minute at most; a second failure is let through to the error boundary, which now wraps every entry — the public /watch, /privacy and /terms pages crashed to a blank page.
This commit is contained in:
+45
-40
@@ -21,10 +21,13 @@ import "./index.css";
|
||||
import "flag-icons/css/flag-icons.min.css";
|
||||
import { lazyWithPreload } from "./lib/lazyWithPreload";
|
||||
import { installClientLog } from "./lib/logHooks";
|
||||
import { installStaleBuildReload } from "./lib/staleBuild";
|
||||
import { appCopyOf } from "./i18n/appCopy";
|
||||
|
||||
// First, before any render: a crash or a navigation during boot belongs in the log too.
|
||||
installClientLog(appCopyOf);
|
||||
// A tab from before the last release reloads onto the new build instead of crashing on a lazy chunk.
|
||||
installStaleBuildReload();
|
||||
|
||||
// Split by top-level route so each entry point is its own chunk: a public /watch share link or a
|
||||
// legal page never downloads the authenticated app bundle, and the app never carries them either.
|
||||
@@ -63,50 +66,52 @@ const root =
|
||||
</FloatingTree>
|
||||
) : (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ErrorBoundary>
|
||||
{/* One tree for the whole app: every anchored popover registers as a node so Escape and
|
||||
outside-press act only on the topmost layer (S3 folds the Modal + players in). */}
|
||||
<FloatingTree>
|
||||
<ConfirmProvider>
|
||||
{/* Nothing below renders until the tab knows which account it is: every provider
|
||||
seeds per-account state in its initializer (see BootGate). */}
|
||||
<BootGate>
|
||||
<NavigationProvider>
|
||||
<FeedFiltersProvider>
|
||||
<PlaylistsProvider>
|
||||
<PlexProvider>
|
||||
<PlexViewBridge>
|
||||
{/* SavedViewsProvider sits below BOTH filter providers so it can switch its
|
||||
source (feed ⟷ plex) by the active page and read the right live filters. */}
|
||||
<SavedViewsProvider>
|
||||
<ChannelsProvider>
|
||||
<FeedViewProvider>
|
||||
<PrefsProvider>
|
||||
<WizardProvider>
|
||||
{/* The same quiet screen as the gate's, so a chunk that is still
|
||||
loading when the gate opens continues it instead of blanking. */}
|
||||
<Suspense fallback={<BootScreen />}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</WizardProvider>
|
||||
</PrefsProvider>
|
||||
</FeedViewProvider>
|
||||
</ChannelsProvider>
|
||||
</SavedViewsProvider>
|
||||
</PlexViewBridge>
|
||||
</PlexProvider>
|
||||
</PlaylistsProvider>
|
||||
</FeedFiltersProvider>
|
||||
</NavigationProvider>
|
||||
</BootGate>
|
||||
</ConfirmProvider>
|
||||
</FloatingTree>
|
||||
</ErrorBoundary>
|
||||
{/* One tree for the whole app: every anchored popover registers as a node so Escape and
|
||||
outside-press act only on the topmost layer (S3 folds the Modal + players in). */}
|
||||
<FloatingTree>
|
||||
<ConfirmProvider>
|
||||
{/* Nothing below renders until the tab knows which account it is: every provider
|
||||
seeds per-account state in its initializer (see BootGate). */}
|
||||
<BootGate>
|
||||
<NavigationProvider>
|
||||
<FeedFiltersProvider>
|
||||
<PlaylistsProvider>
|
||||
<PlexProvider>
|
||||
<PlexViewBridge>
|
||||
{/* SavedViewsProvider sits below BOTH filter providers so it can switch its
|
||||
source (feed ⟷ plex) by the active page and read the right live filters. */}
|
||||
<SavedViewsProvider>
|
||||
<ChannelsProvider>
|
||||
<FeedViewProvider>
|
||||
<PrefsProvider>
|
||||
<WizardProvider>
|
||||
{/* The same quiet screen as the gate's, so a chunk that is still
|
||||
loading when the gate opens continues it instead of blanking. */}
|
||||
<Suspense fallback={<BootScreen />}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</WizardProvider>
|
||||
</PrefsProvider>
|
||||
</FeedViewProvider>
|
||||
</ChannelsProvider>
|
||||
</SavedViewsProvider>
|
||||
</PlexViewBridge>
|
||||
</PlexProvider>
|
||||
</PlaylistsProvider>
|
||||
</FeedFiltersProvider>
|
||||
</NavigationProvider>
|
||||
</BootGate>
|
||||
</ConfirmProvider>
|
||||
</FloatingTree>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<Suspense fallback={<div style={{ minHeight: "100vh" }} />}>{root}</Suspense>
|
||||
{/* Around EVERY entry, the public ones too: a crash on a shared /watch link, or its chunk gone
|
||||
twice over after a release (lib/staleBuild), left a blank white page with no way on. */}
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<div style={{ minHeight: "100vh" }} />}>{root}</Suspense>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user