Skip to content

React Router browser tracing - Lazy imported routes with suspense start transaction spans with wrong path #15027

Open
@peteragarclover

Description

Is there an existing issue for this?

How do you use Sentry?

Self-hosted/on-premise

Which SDK are you using?

@sentry/react

SDK Version

8.47.0

Framework Version

React 18.3.1

Link to Sentry event

No response

Reproduction Example/SDK Setup

Init sentry with React Router browser tracing integration.

Sentry.init({
  dsn,
  release,
  environment,
  attachStacktrace: true,
  tracePropagationTargets,
  integrations: [
    Sentry.reactRouterV7BrowserTracingIntegration({
      useEffect,
      useLocation,
      useNavigationType,
      createRoutesFromChildren,
      matchRoutes,
    }),
  ],
  tracesSampleRate,
});

Configure React Router with Sentry wrapper. But Lazy import routes, e.g. something like:

// user routes bundle loaded lazily
const UserRoutes = lazy(() => import("./UserRoutes"));

const Router: FC = () => {
  const { user } = useContext(UserContext);

  const sentryCreateBrowserRouter: typeof createBrowserRouter =
    Sentry.wrapCreateBrowserRouterV7(createBrowserRouter);

  const router = sentryCreateBrowserRouter(
    createRoutesFromElements(
      <>
        <Route path="login" element={<Login />} />
        <Route
          path="*"
          element={
            user ? (
              // All other routes for logged in users
              <Suspense fallback={<LoadingPage />}>
                <UserRoutes />
              </Suspense>
            ) : (
              // All other routes when not logged in, just navigate back to login page
              <Navigate to="/login" />
            )
          }
        />
      </>,
    ),
  );

Steps to Reproduce

  1. Configured React Router with Sentry Wrappers
  2. Lazy load routes
  3. Navigate to "/" (This initializes Sentry's route change subscriptions using routes available on initial page load)
  4. Navigate to lazy loaded routes, e.g. "/profile"

Expected Result

Navigation transaction span started using path "/profile"

Actual Result

Navigation transaction span started using path "/*"

This appears to be because the routes are only extracted once on mount:

_useEffect(
() => {
const routes = _createRoutesFromChildren(props.children) as RouteObject[];
if (isMountRenderPass.current) {
routes.forEach(route => {
const extractedChildRoutes = getChildRoutesRecursively(route);
extractedChildRoutes.forEach(r => {
allRoutes.add(r);
});
});
updatePageloadTransaction(getActiveRootSpan(), location, routes, undefined, undefined, Array.from(allRoutes));
isMountRenderPass.current = false;
} else {
handleNavigation({
location,
routes,
navigationType,
version,
allRoutes: Array.from(allRoutes),
});
}
},
// `props.children` is purposely not included in the dependency array, because we do not want to re-run this effect
// when the children change. We only want to start transactions when the location or navigation type change.
[location, navigationType],
);

And even though additional routes are lazy loaded, and the route name is correctly passed from the navigation event, when the name is normalized, it can't find the route, so falls back to the parent catch-all "/*"

const isInDescendantRoute = locationIsInsideDescendantRoute(location, allRoutes || routes);
if (isInDescendantRoute) {
name = prefixWithSlash(rebuildRoutePathFromAllRoutes(allRoutes || routes, location));
source = 'route';
}
if (!isInDescendantRoute || !name) {
[name, source] = getNormalizedName(routes, location, branches, basename);
}

Metadata

Assignees

Labels

Package: reactIssues related to the Sentry React SDK

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions