Which project does this relate to?
Router
Describe the bug
When a code-split route chunk 404s after a deploy, lazyRouteComponent sets the tanstack_router_reload:<message> sessionStorage key, calls window.location.reload() and suspends with a never-resolving promise. That part works.
The problem is what happens between the reload() call and the navigation actually committing. reload() is asynchronous, and the error variable in the closure is never cleared. If Lazy re-renders during that gap (in our case the router store settling after the navigation is enough), the guard key is already set, so the if (!sessionStorage.getItem(storageKey)) check fails and the component falls through to throw error. The route's errorComponent then renders the Failed to fetch dynamically imported module TypeError for a moment, until the reload lands and the page comes back healthy.
This is point 2 in the description of #3262 ("I'm not 100% sure a re-render is possible here"). It is: our users report the error screen appearing and then the page refreshing on its own, and the error our errorComponent receives is that TypeError, which can only reach it through that branch.
The Solid and Vue lazyRouteComponent implementations have the same structure.
Steps to Reproduce the Bug or Issue
- Build an app with automatic code splitting and a
defaultErrorComponent.
- Open it in a tab and stay on a route without refreshing.
- Deploy a new build so the chunk hashes change and the old chunks are no longer served.
- In the same tab, navigate to a route whose chunk has not been loaded yet.
Expected behavior
The pending state stays up until the reload completes.
Actual behavior
The errorComponent flashes with the import error, then the page reloads and works.
Proposed fix
Remember in the closure that a reload has been requested, and keep suspending on later renders instead of re-evaluating the sessionStorage guard:
let reloadRequested = false
const lazyComp = function Lazy(props: any) {
if (error) {
if (reloadRequested) {
throw new Promise(() => {})
}
if (
isModuleNotFoundError(error) &&
!(isServer ?? typeof window === 'undefined') &&
typeof sessionStorage !== 'undefined'
) {
const storageKey = `tanstack_router_reload:${error.message}`
if (!sessionStorage.getItem(storageKey)) {
sessionStorage.setItem(storageKey, '1')
reloadRequested = true
window.location.reload()
throw new Promise(() => {})
}
}
throw error
}
// ...
}
The sessionStorage guard still prevents a reload loop across page loads; the closure flag only covers renders within the page that is already reloading.
Platform
- Router / Start Version: 1.170.35 (same code on
main)
- Bundler: Vite
- Browser: Chrome
Which project does this relate to?
Router
Describe the bug
When a code-split route chunk 404s after a deploy,
lazyRouteComponentsets thetanstack_router_reload:<message>sessionStorage key, callswindow.location.reload()and suspends with a never-resolving promise. That part works.The problem is what happens between the
reload()call and the navigation actually committing.reload()is asynchronous, and theerrorvariable in the closure is never cleared. IfLazyre-renders during that gap (in our case the router store settling after the navigation is enough), the guard key is already set, so theif (!sessionStorage.getItem(storageKey))check fails and the component falls through tothrow error. The route'serrorComponentthen renders theFailed to fetch dynamically imported moduleTypeError for a moment, until the reload lands and the page comes back healthy.This is point 2 in the description of #3262 ("I'm not 100% sure a re-render is possible here"). It is: our users report the error screen appearing and then the page refreshing on its own, and the error our
errorComponentreceives is that TypeError, which can only reach it through that branch.The Solid and Vue
lazyRouteComponentimplementations have the same structure.Steps to Reproduce the Bug or Issue
defaultErrorComponent.Expected behavior
The pending state stays up until the reload completes.
Actual behavior
The
errorComponentflashes with the import error, then the page reloads and works.Proposed fix
Remember in the closure that a reload has been requested, and keep suspending on later renders instead of re-evaluating the sessionStorage guard:
The sessionStorage guard still prevents a reload loop across page loads; the closure flag only covers renders within the page that is already reloading.
Platform
main)