-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(react): Add transaction name guards for rapid lazy-route navigations #18346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds transaction name guards to prevent corruption when users rapidly navigate between lazy routes before lazy handlers complete resolution. The fix captures the location at the time a lazy handler is invoked and verifies it still matches the current location before updating transaction names.
- Captures window location when lazy route handlers are invoked
- Adds guard logic to skip span updates if user has navigated away
- Includes comprehensive e2e tests for various rapid navigation scenarios
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/reactrouter-compat-utils/lazy-routes.tsx | Added captureCurrentLocation() function and modified proxy to capture location at invocation time, passing it through to processResolvedRoutes |
| packages/react/src/reactrouter-compat-utils/instrumentation.tsx | Added guard logic to compare captured location with current window location, skipping span updates when they don't match |
| packages/react/test/reactrouter-compat-utils/lazy-routes.test.ts | Updated test expectations to include new currentLocation parameter and added test for null-to-undefined conversion |
| dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts | Added seven new e2e tests covering rapid navigation, zero-wait navigation, browser back, and multiple overlapping handlers |
| dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/SlowFetchLazyRoutes.tsx | New test page with 500ms delay and top-level await to simulate slow lazy route loading |
| dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/Index.tsx | Added navigation link to slow-fetch route for testing |
| dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/DelayedLazyRoute.tsx | Added navigation links for testing rapid navigation between routes |
| dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx | Added slow-fetch route configuration with lazy handler |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| const windowLocation = WINDOW.location; | ||
| if (windowLocation) { | ||
| const capturedKey = computeLocationKey(currentLocation); | ||
| const currentKey = `${windowLocation.pathname}${windowLocation.search || ''}${windowLocation.hash || ''}`; |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location key is computed twice: once using computeLocationKey() for currentLocation, and once manually for windowLocation. This duplicates the logic and increases the risk of inconsistency. Consider using computeLocationKey() for both, or extracting the manual computation into a reusable helper that accepts a window.location-like object.
| const currentKey = `${windowLocation.pathname}${windowLocation.search || ''}${windowLocation.hash || ''}`; | |
| const currentKey = computeLocationKey(windowLocation); |
| expect(mockProcessResolvedRoutes).toHaveBeenCalledWith(routes, route, mockLocation); | ||
| }); | ||
|
|
||
| it('should pass null location when currentLocation is null', () => { |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test name "should pass null location when currentLocation is null" is misleading because the test actually verifies that null is converted to undefined when passed to processResolvedRoutes. Consider renaming to "should convert null location to undefined for processResolvedRoutes" to better reflect what is being tested.
| it('should pass null location when currentLocation is null', () => { | |
| it('should convert null location to undefined for processResolvedRoutes', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
4f05cf5 to
1deccb8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
29a8700 to
1deccb8
Compare
9d875a8 to
b270752
Compare
b270752 to
8ae6a96
Compare
Following up on #18098 and #18155
Fixes a race condition where rapid navigations between lazy routes cause transaction names to be incorrectly assigned or corrupted.
This occurs when async lazy handlers resolve after the user already navigated to a different route. The root cause was
captureCurrentLocation()was readingwindow.locationat the time of handler invocation, but duringpatchRoutesOnNavigation, the browser URL hasn't actually updated yet. So when handlers invoked during navigation A resolve later, they would capture navigation B's location (or whatever the current URL is), leading to incorrect span updates.The fix introduces a navigation context mechanism that captures both the correct
targetPathand the active span at the start ofpatchRoutesOnNavigation, making them available to async handler proxies during invocation. This ensures handlers always use the navigation context they were invoked with, not the current browser state.The navigation context uses a stack to properly handle overlapping/concurrent
patchRoutesOnNavigationcalls. If a second navigation starts before the first one's handlers complete, each navigation maintains its own captured context.