Fix stuck onboarding route on failed chunk preload (#23359)
Fixes [Sentry 7604159654](https://sentry.io/issues/7604159654/) (v2.20.0, Mobile Safari). The onboarding router preloads 7 lazy chunks on entry; Vite's CSS preload for SyncEmails rejected and three defects compounded: - `void SomePage.preload()` discarded the promise, so it became an unhandled rejection and the user got a raw `Unable to preload CSS for /assets/...css` snackbar. - `lazyWithPreload` cached the *rejected* promise and rendered via `throw preload()`. React pings on the rejection, re-renders, the component throws the same settled rejected thenable, the ping listener de-dupes, and the route hangs on its loader forever. - `checkIfItsAViteStaleChunkLazyLoadingError` only matched Chrome's message, so `AppErrorBoundary`'s reload recovery never fired for the CSS-preload or Safari variants. `lazyWithPreload` now records the failure in state instead of rethrowing, so the thenable thrown into Suspense always fulfills, `preload()` returns void and can never reject, and the render path throws the real `Error` to the boundary, which reloads. Two things worth knowing for review: `React.lazy` is not a substitute here (its initializer has no synchronous fast path, so it suspends even when the module is already loaded, reintroducing the loader flash #22392 removed), and the failure is deliberately sticky because Vite marks the dep `seen` before attempting it, so an in-document retry loads the JS without its CSS and silently renders an unstyled page. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23359?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -124,13 +124,13 @@ const NotFound = lazy(() =>
|
||||
);
|
||||
|
||||
const preloadOnboardingPages = () => {
|
||||
void WorkspaceActivation.preload();
|
||||
void CreateProfile.preload();
|
||||
void SyncEmails.preload();
|
||||
void InstallApps.preload();
|
||||
void InviteTeam.preload();
|
||||
void ChooseYourPlan.preload();
|
||||
void WorkspaceSetup.preload();
|
||||
WorkspaceActivation.preload();
|
||||
CreateProfile.preload();
|
||||
SyncEmails.preload();
|
||||
InstallApps.preload();
|
||||
InviteTeam.preload();
|
||||
ChooseYourPlan.preload();
|
||||
WorkspaceSetup.preload();
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
+28
@@ -11,6 +11,34 @@ describe('checkIfItsAViteStaleChunkLazyLoadingError', () => {
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for the Firefox dynamic import failure message', () => {
|
||||
const error = new Error(
|
||||
'error loading dynamically imported module: /some/module.js',
|
||||
);
|
||||
|
||||
const result = checkIfItsAViteStaleChunkLazyLoadingError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for the Safari dynamic import failure message', () => {
|
||||
const error = new Error('Importing a module script failed.');
|
||||
|
||||
const result = checkIfItsAViteStaleChunkLazyLoadingError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when a CSS chunk fails to preload', () => {
|
||||
const error = new Error(
|
||||
'Unable to preload CSS for /assets/SyncEmails-DKxn4rm-.css',
|
||||
);
|
||||
|
||||
const result = checkIfItsAViteStaleChunkLazyLoadingError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when error message does not contain the Vite stale chunk error text', () => {
|
||||
const error = new Error('Some other error message');
|
||||
|
||||
|
||||
+10
-1
@@ -1,3 +1,12 @@
|
||||
const VITE_STALE_CHUNK_ERROR_MESSAGES = [
|
||||
'Failed to fetch dynamically imported module',
|
||||
'error loading dynamically imported module',
|
||||
'Importing a module script failed',
|
||||
'Unable to preload CSS for',
|
||||
];
|
||||
|
||||
export const checkIfItsAViteStaleChunkLazyLoadingError = (error: Error) => {
|
||||
return error.message.includes('Failed to fetch dynamically imported module');
|
||||
return VITE_STALE_CHUNK_ERROR_MESSAGES.some((staleChunkErrorMessage) =>
|
||||
error.message.includes(staleChunkErrorMessage),
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user