-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(react): Defer React Router span finalization until lazy routes load #18881
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
c7953b2 to
c11361b
Compare
size-limit report 📦
|
34ccaca to
c47669c
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
This PR fixes a critical issue where pageload transactions receive incorrect names (URL-based or wildcard-based) when lazy routes load after the span ends due to idle timeout. The fix introduces a deferred promise mechanism to block span finalization until patchRoutesOnNavigation completes, ensuring lazy-loaded routes are available for proper transaction naming.
Changes:
- Added deferred promise mechanism that blocks span finalization until lazy routes finish loading via
patchRoutesOnNavigation - Captured active span before router creation (instead of after) to maintain reference even if span ends before lazy routes load
- Modified
patchSpanEndto use globalallRoutesSet directly instead of a snapshot, allowing visibility of routes added after span starts - Extended
patchRoutesOnNavigationwrapper to handle both pageload and navigation spans (previously only navigation)
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
packages/react/src/reactrouter-compat-utils/instrumentation.tsx |
Core implementation: adds deferred promise mechanism, moves span capture timing, updates patchSpanEnd to use global allRoutes, extends patchRoutesOnNavigation to handle pageload spans |
packages/react/test/reactrouter-compat-utils/instrumentation.test.tsx |
Unit tests verifying allRoutes global set behavior and proper handling of late route additions |
dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts |
E2E regression tests verifying slow lazy routes get parameterized names even with early span end |
dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/WildcardLazyRoutes.tsx |
New test component simulating slow lazy route loading with wildcard routes |
dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx |
Router configuration for wildcard lazy route test scenario |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/react/src/reactrouter-compat-utils/instrumentation.tsx
Outdated
Show resolved
Hide resolved
packages/react/src/reactrouter-compat-utils/instrumentation.tsx
Outdated
Show resolved
Hide resolved
packages/react/src/reactrouter-compat-utils/instrumentation.tsx
Outdated
Show resolved
Hide resolved
...ages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/WildcardLazyRoutes.tsx
Show resolved
Hide resolved
packages/react/src/reactrouter-compat-utils/instrumentation.tsx
Outdated
Show resolved
Hide resolved
b578154 to
a9ed0f4
Compare
chargome
left a comment
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.
Looks good but the code for this becomes increasingly difficult to read. We should add a state/mermaid diagram at some point for more clarity on how we do parameterization here
Thanks for fixing!
| for (const route of allRoutes) { | ||
| const idMatches = route.id !== undefined && route.id === routeId; | ||
| const referenceMatches = route === leafRoute; | ||
| const pathMatches = | ||
| route.path !== undefined && leafRoute.path !== undefined && route.path === leafRoute.path; | ||
|
|
||
| if (idMatches || referenceMatches || pathMatches) { | ||
| // Attach children to this parent route | ||
| addResolvedRoutesToParent(children, route); | ||
| break; | ||
| } | ||
| } |
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.
You could use .find() here (fewer bytes in bundle size and readable):
const matchingRoute = allRoutes.find(route => {
const idMatches = route.id !== undefined && route.id === routeId;
const referenceMatches = route === leafRoute;
const pathMatches = route.path !== undefined && leafRoute.path !== undefined && route.path === leafRoute.path;
return idMatches || referenceMatches || pathMatches;
});
if (matchingRoute) {
addResolvedRoutesToParent(children, targetRoute);
}
I agree, I'll add a dev-docs with diagrams after we're eventually sure we eliminated all potential edge cases. |
node-overhead report 🧳Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.
|
Following up on #18155 and #18346
Fixes an issue where pageload transactions have incorrect names (URL-based or wildcard-based) when lazy routes load after the span ends due to idle timeout.
This occurs when using patchRoutesOnNavigation for lazy route loading. The idle timeout can fire before lazy routes finish loading, causing the span to end with a wildcard like
/slow-fetch/*instead of the parameterized/slow-fetch/:id.The root cause was that the active span was captured after router creation, making it inaccessible when
patchRoutesOnNavigationwas called later (span already ended). Also,patchSpanEndwas taking a snapshot ofallRoutes, so lazy routes added later wouldn't be visible.This fix:
wrapPatchRoutesOnNavigationpatchRoutesOnNavigationcompletesallRoutesSet directly instead of a snapshotpatchRoutesOnNavigation(was only handling navigation before)