Uh oh!
There was an error while loading. Please reload this page.
fix(react): Patch spanEnd for potentially cancelled lazy-route transactions - #17962
Conversation
a09c3a7 to
03e62e7ComparespanEnd for potentially cancelled lazy-route pageloadsspanEnd for potentially cancelled lazy-route transactions03e62e7 to
7c622f8Comparesize-limit report 📦
|
20d8466 to
3345b67Comparenode-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.
|
5ac0764 to
e7270bcCompareUh oh!
There was an error while loading. Please reload this page.
| * Patches the span.end() method to update the transaction name one last time before the span is sent. | ||
| * This handles cases where the span is cancelled early (e.g., document.hidden) before lazy routes have finished loading. | ||
| */ | ||
| function patchSpanEnd( |
There was a problem hiding this comment.
We have client.on("spanEnd"). Is it possible to use it here? Then it would be more transparent and aligned with the other implementations.
There was a problem hiding this comment.
I think that's not usable in this case, as we need to update the span name/source right before it ends. Looks like client.on('spanEnd') runs when the span is not mutable anymore.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…tions (#18155) Building on top of #17962 Added a few more checks to make sure non-resolved (wildcard) routes are not reported in lazy route pageloads / navigations. - Improved `patchSpanEnd` with a user-configurable wait timeout for potentially slow route resolution. Named this option as `lazyRouteTimeout` and it's defaulted as `idleTimeout` * 3. It may conditionally delay reporting (if the route resolution is still not done by the end of the timeout), but will prevent prematurely sent lazy-route transactions inside that window. - Added extra checks on `updateNavigationSpan` and `handleNavigation` for whether any wildcard still exists in a lazy-route, so they are still marked as open to full resolution. We keep track of pending lazy-route resolutions inside `pendingLazyRouteLoads` - Added a final attempt to update the transaction name with fully-resolved route when the pending resolution is done. Any of these should not affect the behaviour of non-lazy route usage --------- Co-authored-by: Sigrid <32902192+s1gr1d@users.noreply.github.com>
…es (#19086) This PR will resolve the core reason for the series of fixes / handling for automatic lazy-route resolution for a while. Related: #18898, #18881, #18346, #18155, #18098, #17962, #17867, #17438, #17277 The core issue we have been trying to tackle is not having access to the complete route hierarchy when asynchronously loaded lazy routes are used. React Router provides a route manifest that we can use while matching parameterized transaction names with routes in all cases except this lazy-routes pattern. This problem has been discussed on React Router: - remix-run/react-router#11113 While this has been [addressed](remix-run/react-router#11626) for Remix / React Router (Framework Mode), it's still not available in Library Mode. The manifest contains the lazily-loaded route, only when it's navigated to. While waiting for navigation, our transactions can be dropped for several reasons, such as user behaviour like switching tabs (`document.hidden` guard), hitting timeouts like `idleTimeout`, and potentially other reasons. This results in incomplete transaction naming with leftover wildcards, which caused broken aggregation on the Sentry dashboard. The series of attempts to fix this while keeping automatic route discovery has been prone to race conditions and required special-case handling of each edge case scenario, also requiring a considerable amount of internal logic, affecting our readability and performance. At the end, all failed in giving completely robust and deterministic results on the customers' side. This PR proposes a new option: `lazyRouteManifest` specifically for lazy routes. This will let us have initial information about the route hierarchy. So we can assign correct parameterized transaction names without needing to wait for navigated state. It's a static array of routes in parameterized format (needs to be maintained by the users on route hierarchy updates) like: ```ts Sentry.reactRouterV7BrowserTracingIntegration({ // ... enableAsyncRouteHandlers: true lazyRouteManifest: [ '/', '/pricing', '/features', '/login', '/signup', '/forgot-password', '/reset-password/:token', '/org/:orgSlug', '/org/:orgSlug/dashboard', '/org/:orgSlug/projects', '/org/:orgSlug/projects/:projectId', '/org/:orgSlug/projects/:projectId/settings', '/org/:orgSlug/projects/:projectId/issues', '/org/:orgSlug/projects/:projectId/issues/:issueId', '/org/:orgSlug/team', '/org/:orgSlug/team/:memberId', '/org/:orgSlug/settings', '/org/:orgSlug/billing', '/admin', '/admin/users', '/admin/users/:userId', '/admin/orgs', '/admin/orgs/:orgId', ], }) ``` - This will only be active when `enableAsyncRouteHandlers` is set to `true` - To match URLs with given routes, we mimic React Router's own implementation. - When this is not provided or fails, it falls back to the current behaviour - This manifest is primarily for lazy routes, but the users can also add their non-lazy routes here for convenience or consistency. - Also added E2E tests that will fail when (if at some point) React Router manifests include the lazy routes before navigation, so we'll be aware and plan depending on that manifest instead. - We can do a cleanup for the race-condition / edge-case handling part of the code in a follow-up PR. Closes#19090 (added automatically)
Fixes an issue where
pageloadandnavigationtransactions have incorrect (URL-based or wildcard-based) names when the span is cancelled early before lazy routes finish loading.This occurs when
document.hiddentriggers early span cancellation (e.g., user switches tabs during page load). In React Router applications with lazy routes, the parameterized route information may not be available yet when the span ends, resulting in transaction names like/user/123/edit(URL-based) or/projects/*/views/*(wildcard-based) instead of the correct parameterized route like/user/:id/editor/projects/:projectId/views/:viewId.This fix patches
span.end()to perform a final route resolution check before the span is sent, using the live globalallRoutesSet to capture any lazy routes that loaded after the span was created but before it ended.