Skip to content

fix(react-router): stop lazyRouteComponent flashing the error component while reloading - #8433

Open
quanglam2807 wants to merge 1 commit into
TanStack:mainfrom
quanglam2807:fix-lazy-route-component-reload-reentry
Open

quanglam2807 wants to merge 1 commit into
TanStack:mainfrom
quanglam2807:fix-lazy-route-component-reload-reentry

Conversation

@quanglam2807

@quanglam2807 quanglam2807 commented Sep 15, 2026

Copy link
Copy Markdown

Fixes #8377.

🎯 Changes

lazyRouteComponent already recovers from a stale chunk after a deploy: it sets a sessionStorage guard, calls window.location.reload() and suspends on a never-resolving promise.

But reload() is asynchronous and error is never cleared, so any render between the reload request and the document going away re-enters the if (error) branch, finds the guard key already set, and falls through to throw error. The route's errorComponent renders the import failure for a moment until the reload lands — and, for anyone wiring the router's defaultOnCatch to error tracking, the failure is reported as unhandled even though the user recovers.

This remembers the reload request in the closure and keeps suspending on later renders instead of re-evaluating the guard:

   let error: any
+  let reloadRequested = false
 
   const lazyComp = function Lazy(props: any) {
     if (error) {
+      if (reloadRequested) {
+        throw new Promise(() => {})
+      }
+
       if (isModuleNotFoundError(error) && ...) {
         const storageKey = `tanstack_router_reload:${error.message}`
         if (!sessionStorage.getItem(storageKey)) {
           sessionStorage.setItem(storageKey, '1')
+          reloadRequested = true
           window.location.reload()

The sessionStorage guard is deliberately untouched. It exists to stop a reload loop when a chunk is missing for some reason other than a new deployment, and it still does: the closure flag only covers renders within the page that is already reloading, so a fresh page load still surfaces the error.

Why this is worth fixing beyond the visual flash

Users do recover, so the flash is minor on its own. The problem is that a recovered failure becomes indistinguishable from an unrecovered one in error tracking. On one of our production apps this is the single largest source of client errors — ~400 events across ~93 issue groups (one per dead chunk URL, since the URL is in the message), at a deploy every 5–10 minutes. We spent a while assuming users were stranded on an error screen before the minified culprit frame showed they were being reloaded correctly the whole time. Details and breadcrumbs in #8377.

Tests

Added packages/react-router/tests/issue-8377-lazy-chunk-reload-reentry.test.tsx, following the existing issue-NNNN convention:

  • A render after the reload is requested keeps suspending instead of throwing, and does not trigger a second reload. Without the source change this fails with expected TypeError: Failed to fetch dynamically im… to be an instance of Promise.
  • A later page load with the guard already set still throws, so the anti-loop behaviour is not weakened. This passes both with and without the fix, on purpose.

Scope

React only. For reference, I checked the other two frameworks:

  • solid-router has the same re-entry: its reload branch returns { default: () => null } instead of throwing, but a second render still reaches throw error. I left it out to keep this reviewable and because I can't exercise it as confidently — happy to add it here or in a follow-up if you'd prefer it in one go.
  • vue-router already guards this with its attemptedReload closure flag, so it needs no change.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with the relevant test commands, or tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

Full @tanstack/react-router suite: 93 files, 1180 passed, 1 skipped, no type errors.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes

    • Fixed lazy-loaded route failures during stale-chunk recovery so the error screen no longer briefly appears while a page reload is pending.
    • Prevented repeated reload requests during the same recovery attempt.
    • Preserved error reporting when the reload safeguard is already active.
  • Tests

    • Added regression coverage for reload behavior and subsequent error handling.

…nt while reloading

`window.location.reload()` is asynchronous, so renders can still happen
between the reload request and the document going away. Those renders
re-read the sessionStorage guard, find it already set, and fall through
to `throw error`, so the route's error component renders the import
failure for a moment before the reload lands.

Remember the reload request in the closure and keep suspending on later
renders instead of re-evaluating the guard. The sessionStorage guard is
untouched, so a chunk missing for any reason other than a new deployment
still surfaces its error on the next page load rather than looping.

Fixes TanStack#8377
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 4d68b52e-47a8-4dba-83d8-481e89a29d03

📥 Commits

Reviewing files that changed from the base of the PR and between 8e164d2 and c6e404b.

📒 Files selected for processing (3)
  • .changeset/lazy-route-component-reload-reentry.md
  • packages/react-router/src/lazyRouteComponent.tsx
  • packages/react-router/tests/issue-8377-lazy-chunk-reload-reentry.test.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Lazy route reload handling

Layer / File(s) Summary
Preserve pending reload state
packages/react-router/src/lazyRouteComponent.tsx, .changeset/lazy-route-component-reload-reentry.md
lazyRouteComponent records when it requests a reload and keeps suspending during subsequent renders. The changeset declares a patch release.
Validate reload re-entry behavior
packages/react-router/tests/issue-8377-lazy-chunk-reload-reentry.test.tsx
Regression tests verify one reload request, continued suspension before document replacement, and error propagation when the session guard already exists.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to c6e40

The reload re-entry behavior and existing anti-loop guard are covered by the changed regression tests. No merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the primary change: preventing the lazyRouteComponent error component from flashing during reload.
Description check ✅ Passed The description follows the required template, explains the motivation and implementation, documents testing and scope, completes the checklist, and includes release impact with a changeset.
Linked Issues check ✅ Passed Issue #8377 requires lazyRouteComponent to remain pending after it requests a stale-chunk reload, while preserving the sessionStorage anti-loop guard. The change adds the closure flag `reloadReque…
Out of Scope Changes check ✅ Passed The changeset, the lazyRouteComponent update, and the regression tests all support Issue #8377. The changes do not add unrelated product behavior or unrelated files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. (1 skipped: 1 …
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lazyRouteComponent renders the error component after it has already triggered the stale-chunk reload

1 participant