Skip to content

Commit feb9a84

Browse files
Sheraffautofix-ci[bot]
andauthoredJan 28, 2025··
fix(react-router): improve error handling for module loading failures in lazyRouteComponent (#3262)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 8662b4c commit feb9a84

File tree

1 file changed

+34
-31
lines changed

1 file changed

+34
-31
lines changed
 

‎packages/react-router/src/lazyRouteComponent.tsx

+34-31
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ import type { AsyncRouteComponent } from './route'
88
// URL to the lazy module.
99
// In that case, we want to attempt one window refresh to get the latest.
1010
function isModuleNotFoundError(error: any): boolean {
11+
// chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
12+
// firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
13+
// safari: "Importing a module script failed."
14+
if (typeof error?.message !== 'string') return false
1115
return (
12-
typeof error?.message === 'string' &&
13-
/Failed to fetch dynamically imported module/.test(error.message)
16+
error.message.startsWith('Failed to fetch dynamically imported module') ||
17+
error.message.startsWith('error loading dynamically imported module') ||
18+
error.message.startsWith('Importing a module script failed')
1419
)
1520
}
1621

@@ -46,6 +51,7 @@ export function lazyRouteComponent<
4651
let loadPromise: Promise<any> | undefined
4752
let comp: T[TKey] | T['default']
4853
let error: any
54+
let reload: boolean
4955

5056
const load = () => {
5157
if (typeof document === 'undefined' && ssr?.() === false) {
@@ -59,7 +65,27 @@ export function lazyRouteComponent<
5965
comp = res[exportName ?? 'default']
6066
})
6167
.catch((err) => {
68+
// We don't want an error thrown from preload in this case, because
69+
// there's nothing we want to do about module not found during preload.
70+
// Record the error, the rest is handled during the render path.
6271
error = err
72+
if (isModuleNotFoundError(error)) {
73+
if (
74+
error instanceof Error &&
75+
typeof window !== 'undefined' &&
76+
typeof sessionStorage !== 'undefined'
77+
) {
78+
// Again, we want to reload one time on module not found error and not enter
79+
// a reload loop if there is some other issue besides an old deploy.
80+
// That's why we store our reload attempt in sessionStorage.
81+
// Use error.message as key because it contains the module path that failed.
82+
const storageKey = `tanstack_router_reload:${error.message}`
83+
if (!sessionStorage.getItem(storageKey)) {
84+
sessionStorage.setItem(storageKey, '1')
85+
reload = true
86+
}
87+
}
88+
}
6389
})
6490
}
6591

@@ -68,36 +94,13 @@ export function lazyRouteComponent<
6894

6995
const lazyComp = function Lazy(props: any) {
7096
// Now that we're out of preload and into actual render path,
71-
// throw the error if it was a module not found error during preload
97+
if (reload) {
98+
// If it was a module loading error,
99+
// throw eternal suspense while we wait for window to reload
100+
window.location.reload()
101+
throw new Promise(() => {})
102+
}
72103
if (error) {
73-
if (isModuleNotFoundError(error)) {
74-
// We don't want an error thrown from preload in this case, because
75-
// there's nothing we want to do about module not found during preload.
76-
// Record the error, recover the promise with a null return,
77-
// and we will attempt module not found resolution during the render path.
78-
79-
if (
80-
error instanceof Error &&
81-
typeof window !== 'undefined' &&
82-
typeof sessionStorage !== 'undefined'
83-
) {
84-
// Again, we want to reload one time on module not found error and not enter
85-
// a reload loop if there is some other issue besides an old deploy.
86-
// That's why we store our reload attempt in sessionStorage.
87-
// Use error.message as key because it contains the module path that failed.
88-
const storageKey = `tanstack_router_reload:${error.message}`
89-
if (!sessionStorage.getItem(storageKey)) {
90-
sessionStorage.setItem(storageKey, '1')
91-
window.location.reload()
92-
93-
// Return empty component while we wait for window to reload
94-
return {
95-
default: () => null,
96-
}
97-
}
98-
}
99-
}
100-
101104
// Otherwise, just throw the error
102105
throw error
103106
}

0 commit comments

Comments
 (0)
Please sign in to comment.