Skip to content

React Router v6 integration: allRoutes accumulates across independent/unrelated routers, causing incorrect transaction names #22782

Description

@masinette

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/react-router

SDK Version

10.68.0

Framework Version

No response

Link to Sentry event

No response

Reproduction Example/SDK Setup

Two independent router trees are each wrapped with Sentry's React Router
instrumentation (e.g. via withSentryReactRouterV6Routing /
wrapCreateBrowserRouter), mounted at different points in the app's
lifecycle, For example, one router for /foo/:fooId and a separate,
unrelated router for /bar/:barId.

// Router A
const RoutesA = () => (
  <SentryRoutes>
    <Route path="/foo/:fooId" element={<FooPage />} />
  </SentryRoutes>
);

// Router B, mounted separately/later
const RoutesB = () => (
  <SentryRoutes>
    <Route path="/bar/:barId" element={<BarPage />} />
  </SentryRoutes>
);

customer provided a full repro app. Link to that repo is included in internal Linear Issue to protect PII

Steps to Reproduce

  1. Mount/navigate within Router A at /foo/123: transaction is correctly
    named /foo/:fooId.
  2. Mount/navigate within Router B at /bar/456.
  3. Observe the transaction name for the /bar/456 navigation.

Expected Result

Transaction name: /bar/:barId

Actual Result

Transaction name is a hybrid/incorrect name such as /bar/:fooId

i.e. the static segment from Router B's path combined with the param
name from Router A's route.

Additional Context

Root cause (if I had to guess)

reactrouterv6-compat-utils.tsx accumulates every route object it has
ever seen into a module-level allRoutes Set, populated via
getChildRoutesRecursively on each wrapped <Routes>'s mount effect,
and never scoped to a router instance or cleared on unmount:

routes.forEach(route => {
  const extractedChildRoutes = getChildRoutesRecursively(route);
  extractedChildRoutes.forEach(r => {
    allRoutes.add(r);
  });
});

This was introduced to support descendant routes / multiple <Routes>
trees that are legitimately part of the same logical route tree (see
#14304). But because allRoutes has no isolation between genuinely
independent router instances, once two unrelated routers have each been
mounted once, matchRoutes/rebuildRoutePathFromAllRoutes operates on
the union of both, and can produce a name that mixes structure from one
router with param names from another.

Priority

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.

Activity

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

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions