From 77cabfbc33cf26a79769c6b1ed6140991b375889 Mon Sep 17 00:00:00 2001
From: Charly Gomez <charly.gomez@sentry.io>
Date: Tue, 7 Jan 2025 09:38:55 +0100
Subject: [PATCH] fix(v8/react): Use `Set` as the `allRoutes` container.
 (#14878) (#14884)

backport of https://github.com/getsentry/sentry-javascript/pull/14878

Co-authored-by: Onur Temizkan <onur@narval.co.uk>
---
 .../src/index.tsx                             |  18 +-
 .../src/pages/Index.tsx                       |   3 +
 .../tests/transactions.test.ts                |  71 ++++
 .../react/src/reactrouterv6-compat-utils.tsx  |  49 ++-
 .../reactrouter-descendant-routes.test.tsx    | 397 ++++++++++++++++++
 packages/react/test/reactrouterv6.test.tsx    | 219 ++--------
 6 files changed, 545 insertions(+), 212 deletions(-)
 create mode 100644 packages/react/test/reactrouter-descendant-routes.test.tsx

diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/index.tsx
index f6694a954915..581014169a78 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/index.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/index.tsx
@@ -3,6 +3,7 @@ import React from 'react';
 import ReactDOM from 'react-dom/client';
 import {
   BrowserRouter,
+  Outlet,
   Route,
   Routes,
   createRoutesFromChildren,
@@ -48,17 +49,28 @@ const DetailsRoutes = () => (
   </SentryRoutes>
 );
 
+const DetailsRoutesAlternative = () => (
+  <SentryRoutes>
+    <Route path=":detailId" element={<div id="details">Details</div>} />
+  </SentryRoutes>
+);
+
 const ViewsRoutes = () => (
   <SentryRoutes>
     <Route index element={<div id="views">Views</div>} />
     <Route path="views/:viewId/*" element={<DetailsRoutes />} />
+    <Route path="old-views/:viewId/*" element={<DetailsRoutesAlternative />} />
   </SentryRoutes>
 );
 
 const ProjectsRoutes = () => (
   <SentryRoutes>
-    <Route path="projects/:projectId/*" element={<ViewsRoutes />}></Route>
-    <Route path="*" element={<div>No Match Page</div>} />
+    <Route path="projects" element={<Outlet />}>
+      <Route index element={<div>Project Page Root</div>} />
+      <Route path="*" element={<Outlet />}>
+        <Route path=":projectId/*" element={<ViewsRoutes />} />
+      </Route>
+    </Route>
   </SentryRoutes>
 );
 
@@ -67,7 +79,7 @@ root.render(
   <BrowserRouter>
     <SentryRoutes>
       <Route path="/" element={<Index />} />
-      <Route path="/*" element={<ProjectsRoutes />}></Route>
+      <Route path="/*" element={<ProjectsRoutes />} />
     </SentryRoutes>
   </BrowserRouter>,
 );
diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/pages/Index.tsx
index aa99b61f89ea..d2362c149f84 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/pages/Index.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/src/pages/Index.tsx
@@ -8,6 +8,9 @@ const Index = () => {
       <Link to="/projects/123/views/456/789" id="navigation">
         navigate
       </Link>
+      <Link to="/projects/123/old-views/345/654" id="old-navigation">
+        navigate old
+      </Link>
     </>
   );
 };
diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/tests/transactions.test.ts
index 23bc0aaabe95..2f13b7cc1eac 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/tests/transactions.test.ts
@@ -10,6 +10,7 @@ test('sends a pageload transaction with a parameterized URL', async ({ page }) =
 
   const rootSpan = await transactionPromise;
 
+  expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
   expect(rootSpan).toMatchObject({
     contexts: {
       trace: {
@@ -24,6 +25,30 @@ test('sends a pageload transaction with a parameterized URL', async ({ page }) =
   });
 });
 
+test('sends a pageload transaction with a parameterized URL - alternative route', async ({ page }) => {
+  const transactionPromise = waitForTransaction('react-router-6-descendant-routes', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+  });
+
+  await page.goto(`/projects/234/old-views/234/567`);
+
+  const rootSpan = await transactionPromise;
+
+  expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
+  expect(rootSpan).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'pageload',
+        origin: 'auto.pageload.react.reactrouter_v6',
+      },
+    },
+    transaction: '/projects/:projectId/old-views/:viewId/:detailId',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+});
+
 test('sends a navigation transaction with a parameterized URL', async ({ page }) => {
   const pageloadTxnPromise = waitForTransaction('react-router-6-descendant-routes', async transactionEvent => {
     return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
@@ -52,6 +77,8 @@ test('sends a navigation transaction with a parameterized URL', async ({ page })
   const linkElement = page.locator('id=navigation');
 
   const [_, navigationTxn] = await Promise.all([linkElement.click(), navigationTxnPromise]);
+
+  expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
   expect(navigationTxn).toMatchObject({
     contexts: {
       trace: {
@@ -65,3 +92,47 @@ test('sends a navigation transaction with a parameterized URL', async ({ page })
     },
   });
 });
+
+test('sends a navigation transaction with a parameterized URL - alternative route', async ({ page }) => {
+  const pageloadTxnPromise = waitForTransaction('react-router-6-descendant-routes', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+  });
+
+  const navigationTxnPromise = waitForTransaction('react-router-6-descendant-routes', async transactionEvent => {
+    return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+  });
+
+  await page.goto(`/`);
+  const pageloadTxn = await pageloadTxnPromise;
+
+  expect(pageloadTxn).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'pageload',
+        origin: 'auto.pageload.react.reactrouter_v6',
+      },
+    },
+    transaction: '/',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+
+  const linkElement = page.locator('id=old-navigation');
+
+  const [_, navigationTxn] = await Promise.all([linkElement.click(), navigationTxnPromise]);
+
+  expect((await page.innerHTML('#root')).includes('Details')).toBe(true);
+  expect(navigationTxn).toMatchObject({
+    contexts: {
+      trace: {
+        op: 'navigation',
+        origin: 'auto.navigation.react.reactrouter_v6',
+      },
+    },
+    transaction: '/projects/:projectId/old-views/:viewId/:detailId',
+    transaction_info: {
+      source: 'route',
+    },
+  });
+});
diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx
index 1211435254a1..6aabe0d0db59 100644
--- a/packages/react/src/reactrouterv6-compat-utils.tsx
+++ b/packages/react/src/reactrouterv6-compat-utils.tsx
@@ -180,7 +180,7 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio
     return origUseRoutes;
   }
 
-  const allRoutes: RouteObject[] = [];
+  const allRoutes: Set<RouteObject> = new Set();
 
   const SentryRoutes: React.FC<{
     children?: React.ReactNode;
@@ -207,10 +207,21 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio
 
       if (isMountRenderPass.current) {
         routes.forEach(route => {
-          allRoutes.push(...getChildRoutesRecursively(route));
+          const extractedChildRoutes = getChildRoutesRecursively(route);
+
+          extractedChildRoutes.forEach(r => {
+            allRoutes.add(r);
+          });
         });
 
-        updatePageloadTransaction(getActiveRootSpan(), normalizedLocation, routes, undefined, undefined, allRoutes);
+        updatePageloadTransaction(
+          getActiveRootSpan(),
+          normalizedLocation,
+          routes,
+          undefined,
+          undefined,
+          Array.from(allRoutes),
+        );
         isMountRenderPass.current = false;
       } else {
         handleNavigation({
@@ -218,7 +229,7 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio
           routes,
           navigationType,
           version,
-          allRoutes,
+          allRoutes: Array.from(allRoutes),
         });
       }
     }, [navigationType, stableLocationParam]);
@@ -343,14 +354,18 @@ function locationIsInsideDescendantRoute(location: Location, routes: RouteObject
   return false;
 }
 
-function getChildRoutesRecursively(route: RouteObject, allRoutes: RouteObject[] = []): RouteObject[] {
-  if (route.children && !route.index) {
-    route.children.forEach(child => {
-      allRoutes.push(...getChildRoutesRecursively(child, allRoutes));
-    });
-  }
+function getChildRoutesRecursively(route: RouteObject, allRoutes: Set<RouteObject> = new Set()): Set<RouteObject> {
+  if (!allRoutes.has(route)) {
+    allRoutes.add(route);
 
-  allRoutes.push(route);
+    if (route.children && !route.index) {
+      route.children.forEach(child => {
+        const childRoutes = getChildRoutesRecursively(child, allRoutes);
+
+        childRoutes.forEach(r => allRoutes.add(r));
+      });
+    }
+  }
 
   return allRoutes;
 }
@@ -513,7 +528,7 @@ export function createV6CompatibleWithSentryReactRouterRouting<P extends Record<
     return Routes;
   }
 
-  const allRoutes: RouteObject[] = [];
+  const allRoutes: Set<RouteObject> = new Set();
 
   const SentryRoutes: React.FC<P> = (props: P) => {
     const isMountRenderPass = React.useRef(true);
@@ -527,10 +542,14 @@ export function createV6CompatibleWithSentryReactRouterRouting<P extends Record<
 
         if (isMountRenderPass.current) {
           routes.forEach(route => {
-            allRoutes.push(...getChildRoutesRecursively(route));
+            const extractedChildRoutes = getChildRoutesRecursively(route);
+
+            extractedChildRoutes.forEach(r => {
+              allRoutes.add(r);
+            });
           });
 
-          updatePageloadTransaction(getActiveRootSpan(), location, routes, undefined, undefined, allRoutes);
+          updatePageloadTransaction(getActiveRootSpan(), location, routes, undefined, undefined, Array.from(allRoutes));
           isMountRenderPass.current = false;
         } else {
           handleNavigation({
@@ -538,7 +557,7 @@ export function createV6CompatibleWithSentryReactRouterRouting<P extends Record<
             routes,
             navigationType,
             version,
-            allRoutes,
+            allRoutes: Array.from(allRoutes),
           });
         }
       },
diff --git a/packages/react/test/reactrouter-descendant-routes.test.tsx b/packages/react/test/reactrouter-descendant-routes.test.tsx
new file mode 100644
index 000000000000..dcc73a2275df
--- /dev/null
+++ b/packages/react/test/reactrouter-descendant-routes.test.tsx
@@ -0,0 +1,397 @@
+import {
+  SEMANTIC_ATTRIBUTE_SENTRY_OP,
+  SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
+  SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
+  createTransport,
+  getCurrentScope,
+  setCurrentClient,
+} from '@sentry/core';
+import { render } from '@testing-library/react';
+import * as React from 'react';
+import {
+  MemoryRouter,
+  Navigate,
+  Outlet,
+  Route,
+  Routes,
+  createRoutesFromChildren,
+  matchRoutes,
+  useLocation,
+  useNavigationType,
+  useRoutes,
+} from 'react-router-6';
+
+import { BrowserClient } from '../src';
+import {
+  reactRouterV6BrowserTracingIntegration,
+  withSentryReactRouterV6Routing,
+  wrapUseRoutesV6,
+} from '../src/reactrouterv6';
+
+const mockStartBrowserTracingPageLoadSpan = jest.fn();
+const mockStartBrowserTracingNavigationSpan = jest.fn();
+
+const mockRootSpan = {
+  updateName: jest.fn(),
+  setAttribute: jest.fn(),
+  getSpanJSON() {
+    return { op: 'pageload' };
+  },
+};
+
+jest.mock('@sentry/browser', () => {
+  const actual = jest.requireActual('@sentry/browser');
+  return {
+    ...actual,
+    startBrowserTracingNavigationSpan: (...args: unknown[]) => {
+      mockStartBrowserTracingNavigationSpan(...args);
+      return actual.startBrowserTracingNavigationSpan(...args);
+    },
+    startBrowserTracingPageLoadSpan: (...args: unknown[]) => {
+      mockStartBrowserTracingPageLoadSpan(...args);
+      return actual.startBrowserTracingPageLoadSpan(...args);
+    },
+  };
+});
+
+jest.mock('@sentry/core', () => {
+  const actual = jest.requireActual('@sentry/core');
+  return {
+    ...actual,
+    getRootSpan: () => {
+      return mockRootSpan;
+    },
+  };
+});
+
+describe('React Router Descendant Routes', () => {
+  function createMockBrowserClient(): BrowserClient {
+    return new BrowserClient({
+      integrations: [],
+      tracesSampleRate: 1,
+      transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})),
+      stackParser: () => [],
+    });
+  }
+
+  beforeEach(() => {
+    jest.clearAllMocks();
+    getCurrentScope().setClient(undefined);
+  });
+
+  describe('withSentryReactRouterV6Routing', () => {
+    it('works with descendant wildcard routes - pageload', () => {
+      const client = createMockBrowserClient();
+      setCurrentClient(client);
+
+      client.addIntegration(
+        reactRouterV6BrowserTracingIntegration({
+          useEffect: React.useEffect,
+          useLocation,
+          useNavigationType,
+          createRoutesFromChildren,
+          matchRoutes,
+        }),
+      );
+      const SentryRoutes = withSentryReactRouterV6Routing(Routes);
+
+      const DetailsRoutes = () => (
+        <SentryRoutes>
+          <Route path=":detailId" element={<div id="details">Details</div>} />
+        </SentryRoutes>
+      );
+
+      const ViewsRoutes = () => (
+        <SentryRoutes>
+          <Route index element={<div id="views">Views</div>} />
+          <Route path="views/:viewId/*" element={<DetailsRoutes />} />
+        </SentryRoutes>
+      );
+
+      const ProjectsRoutes = () => (
+        <SentryRoutes>
+          <Route path="projects/:projectId/*" element={<ViewsRoutes />}></Route>
+          <Route path="*" element={<div>No Match Page</div>} />
+        </SentryRoutes>
+      );
+
+      const { container } = render(
+        <MemoryRouter initialEntries={['/projects/000/views/111/222']}>
+          <SentryRoutes>
+            <Route path="/*" element={<ProjectsRoutes />}></Route>
+          </SentryRoutes>
+        </MemoryRouter>,
+      );
+
+      expect(container.innerHTML).toContain('Details');
+
+      expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1);
+      expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/projects/:projectId/views/:viewId/:detailId');
+      expect(mockRootSpan.setAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
+    });
+
+    it('works with descendant wildcard routes - navigation', () => {
+      const client = createMockBrowserClient();
+      setCurrentClient(client);
+
+      client.addIntegration(
+        reactRouterV6BrowserTracingIntegration({
+          useEffect: React.useEffect,
+          useLocation,
+          useNavigationType,
+          createRoutesFromChildren,
+          matchRoutes,
+        }),
+      );
+      const SentryRoutes = withSentryReactRouterV6Routing(Routes);
+
+      const DetailsRoutes = () => (
+        <SentryRoutes>
+          <Route path=":detailId" element={<div id="details">Details</div>} />
+        </SentryRoutes>
+      );
+
+      const ViewsRoutes = () => (
+        <SentryRoutes>
+          <Route index element={<div id="views">Views</div>} />
+          <Route path="views/:viewId/*" element={<DetailsRoutes />} />
+        </SentryRoutes>
+      );
+
+      const ProjectsRoutes = () => (
+        <SentryRoutes>
+          <Route path="projects/:projectId/*" element={<ViewsRoutes />}></Route>
+          <Route path="*" element={<div>No Match Page</div>} />
+        </SentryRoutes>
+      );
+
+      const { container } = render(
+        <MemoryRouter initialEntries={['/']}>
+          <SentryRoutes>
+            <Route index element={<Navigate to="/projects/123/views/234/567" />} />
+            <Route path="/*" element={<ProjectsRoutes />}></Route>
+          </SentryRoutes>
+        </MemoryRouter>,
+      );
+
+      expect(container.innerHTML).toContain('Details');
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
+        name: '/projects/:projectId/views/:viewId/:detailId',
+        attributes: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
+        },
+      });
+    });
+
+    it('works with descendant wildcard routes with outlets', () => {
+      const client = createMockBrowserClient();
+      setCurrentClient(client);
+
+      client.addIntegration(
+        reactRouterV6BrowserTracingIntegration({
+          useEffect: React.useEffect,
+          useLocation,
+          useNavigationType,
+          createRoutesFromChildren,
+          matchRoutes,
+        }),
+      );
+      const SentryRoutes = withSentryReactRouterV6Routing(Routes);
+
+      const DetailsRoutes = () => (
+        <SentryRoutes>
+          <Route path=":detailId" element={<div id="details">Details</div>} />
+        </SentryRoutes>
+      );
+
+      const ViewsRoutes = () => (
+        <SentryRoutes>
+          <Route index element={<div id="views">Views</div>} />
+          <Route path="views/:viewId/*" element={<DetailsRoutes />} />
+        </SentryRoutes>
+      );
+
+      const ProjectsRoutes = () => (
+        <SentryRoutes>
+          <Route path="projects" element={<Outlet />}>
+            <Route index element={<div>Project Page Root</div>} />
+            <Route path="*" element={<Outlet />}>
+              <Route path=":projectId/*" element={<ViewsRoutes />} />
+            </Route>
+          </Route>
+        </SentryRoutes>
+      );
+
+      const { container } = render(
+        <MemoryRouter initialEntries={['/']}>
+          <SentryRoutes>
+            <Route index element={<Navigate to="/projects/123/views/234/567" />} />
+            <Route path="/*" element={<ProjectsRoutes />}></Route>
+          </SentryRoutes>
+        </MemoryRouter>,
+      );
+
+      expect(container.innerHTML).toContain('Details');
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
+        name: '/projects/:projectId/views/:viewId/:detailId',
+        attributes: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
+        },
+      });
+    });
+  });
+
+  describe('wrapUseRoutesV6', () => {
+    it('works with descendant wildcard routes - pageload', () => {
+      const client = createMockBrowserClient();
+      setCurrentClient(client);
+
+      client.addIntegration(
+        reactRouterV6BrowserTracingIntegration({
+          useEffect: React.useEffect,
+          useLocation,
+          useNavigationType,
+          createRoutesFromChildren,
+          matchRoutes,
+        }),
+      );
+
+      const wrappedUseRoutes = wrapUseRoutesV6(useRoutes);
+
+      const DetailsRoutes = () =>
+        wrappedUseRoutes([
+          {
+            path: ':detailId',
+            element: <div id="details">Details</div>,
+          },
+        ]);
+
+      const ViewsRoutes = () =>
+        wrappedUseRoutes([
+          {
+            index: true,
+            element: <div id="views">Views</div>,
+          },
+          {
+            path: 'views/:viewId/*',
+            element: <DetailsRoutes />,
+          },
+        ]);
+
+      const ProjectsRoutes = () =>
+        wrappedUseRoutes([
+          {
+            path: 'projects/:projectId/*',
+            element: <ViewsRoutes />,
+          },
+          {
+            path: '*',
+            element: <div>No Match Page</div>,
+          },
+        ]);
+
+      const Routes = () =>
+        wrappedUseRoutes([
+          {
+            path: '/*',
+            element: <ProjectsRoutes />,
+          },
+        ]);
+
+      const { container } = render(
+        <MemoryRouter initialEntries={['/projects/123/views/456/789']}>
+          <Routes />
+        </MemoryRouter>,
+      );
+
+      expect(container.innerHTML).toContain('Details');
+      expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1);
+      expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/projects/:projectId/views/:viewId/:detailId');
+      expect(mockRootSpan.setAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
+    });
+
+    it('works with descendant wildcard routes - navigation', () => {
+      const client = createMockBrowserClient();
+      setCurrentClient(client);
+
+      client.addIntegration(
+        reactRouterV6BrowserTracingIntegration({
+          useEffect: React.useEffect,
+          useLocation,
+          useNavigationType,
+          createRoutesFromChildren,
+          matchRoutes,
+        }),
+      );
+
+      const wrappedUseRoutes = wrapUseRoutesV6(useRoutes);
+
+      const DetailsRoutes = () =>
+        wrappedUseRoutes([
+          {
+            path: ':detailId',
+            element: <div id="details">Details</div>,
+          },
+        ]);
+
+      const ViewsRoutes = () =>
+        wrappedUseRoutes([
+          {
+            index: true,
+            element: <div id="views">Views</div>,
+          },
+          {
+            path: 'views/:viewId/*',
+            element: <DetailsRoutes />,
+          },
+        ]);
+
+      const ProjectsRoutes = () =>
+        wrappedUseRoutes([
+          {
+            path: 'projects/:projectId/*',
+            element: <ViewsRoutes />,
+          },
+          {
+            path: '*',
+            element: <div>No Match Page</div>,
+          },
+        ]);
+
+      const Routes = () =>
+        wrappedUseRoutes([
+          {
+            index: true,
+            element: <Navigate to="/projects/123/views/456/789" />,
+          },
+          {
+            path: '/*',
+            element: <ProjectsRoutes />,
+          },
+        ]);
+
+      const { container } = render(
+        <MemoryRouter initialEntries={['/']}>
+          <Routes />
+        </MemoryRouter>,
+      );
+
+      expect(container.innerHTML).toContain('Details');
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
+        name: '/projects/:projectId/views/:viewId/:detailId',
+        attributes: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
+        },
+      });
+    });
+  });
+});
diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx
index 33e330c2e232..f61436667210 100644
--- a/packages/react/test/reactrouterv6.test.tsx
+++ b/packages/react/test/reactrouterv6.test.tsx
@@ -491,7 +491,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
       });
     });
 
-    it('works with descendant wildcard routes - pageload', () => {
+    it('works under a slash route with a trailing slash', () => {
       const client = createMockBrowserClient();
       setCurrentClient(client);
 
@@ -506,40 +506,31 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
       );
       const SentryRoutes = withSentryReactRouterV6Routing(Routes);
 
-      const DetailsRoutes = () => (
-        <SentryRoutes>
-          <Route path=":detailId" element={<div id="details">Details</div>} />
-        </SentryRoutes>
-      );
-
-      const ViewsRoutes = () => (
-        <SentryRoutes>
-          <Route index element={<div id="views">Views</div>} />
-          <Route path="views/:viewId/*" element={<DetailsRoutes />} />
-        </SentryRoutes>
-      );
-
-      const ProjectsRoutes = () => (
-        <SentryRoutes>
-          <Route path="projects/:projectId/*" element={<ViewsRoutes />}></Route>
-          <Route path="*" element={<div>No Match Page</div>} />
-        </SentryRoutes>
-      );
-
       render(
-        <MemoryRouter initialEntries={['/projects/000/views/111/222']}>
+        <MemoryRouter initialEntries={['/']}>
           <SentryRoutes>
-            <Route path="/*" element={<ProjectsRoutes />}></Route>
+            <Route index element={<Navigate to="/issues/123/" />} />
+            <Route path="/" element={<div>root</div>}>
+              <Route path="/issues/:groupId/" element={<div>issues group</div>}>
+                <Route index element={<div>index</div>} />
+              </Route>
+            </Route>
           </SentryRoutes>
         </MemoryRouter>,
       );
 
-      expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1);
-      expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/projects/:projectId/views/:viewId/:detailId');
-      expect(mockRootSpan.setAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
+      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
+        name: '/issues/:groupId/',
+        attributes: {
+          [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
+          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
+          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
+        },
+      });
     });
 
-    it('works with descendant wildcard routes - navigation', () => {
+    it('works nested under a slash root without a trailing slash', () => {
       const client = createMockBrowserClient();
       setCurrentClient(client);
 
@@ -554,38 +545,22 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
       );
       const SentryRoutes = withSentryReactRouterV6Routing(Routes);
 
-      const DetailsRoutes = () => (
-        <SentryRoutes>
-          <Route path=":detailId" element={<div id="details">Details</div>} />
-        </SentryRoutes>
-      );
-
-      const ViewsRoutes = () => (
-        <SentryRoutes>
-          <Route index element={<div id="views">Views</div>} />
-          <Route path="views/:viewId/*" element={<DetailsRoutes />} />
-        </SentryRoutes>
-      );
-
-      const ProjectsRoutes = () => (
-        <SentryRoutes>
-          <Route path="projects/:projectId/*" element={<ViewsRoutes />}></Route>
-          <Route path="*" element={<div>No Match Page</div>} />
-        </SentryRoutes>
-      );
-
       render(
         <MemoryRouter initialEntries={['/']}>
           <SentryRoutes>
-            <Route index element={<Navigate to="/projects/123/views/234/567" />} />
-            <Route path="/*" element={<ProjectsRoutes />}></Route>
+            <Route index element={<Navigate to="/issues/123" />} />
+            <Route path="/" element={<div>root</div>}>
+              <Route path="/issues/:groupId/" element={<div>issues group</div>}>
+                <Route index element={<div>index</div>} />
+              </Route>
+            </Route>
           </SentryRoutes>
         </MemoryRouter>,
       );
 
       expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
       expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
-        name: '/projects/:projectId/views/:viewId/:detailId',
+        name: '/issues/:groupId/',
         attributes: {
           [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
           [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
@@ -1214,150 +1189,6 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
       });
     });
 
-    it('works with descendant wildcard routes - pageload', () => {
-      const client = createMockBrowserClient();
-      setCurrentClient(client);
-
-      client.addIntegration(
-        reactRouterV6BrowserTracingIntegration({
-          useEffect: React.useEffect,
-          useLocation,
-          useNavigationType,
-          createRoutesFromChildren,
-          matchRoutes,
-        }),
-      );
-
-      const wrappedUseRoutes = wrapUseRoutesV6(useRoutes);
-
-      const DetailsRoutes = () =>
-        wrappedUseRoutes([
-          {
-            path: ':detailId',
-            element: <div id="details">Details</div>,
-          },
-        ]);
-
-      const ViewsRoutes = () =>
-        wrappedUseRoutes([
-          {
-            index: true,
-            element: <div id="views">Views</div>,
-          },
-          {
-            path: 'views/:viewId/*',
-            element: <DetailsRoutes />,
-          },
-        ]);
-
-      const ProjectsRoutes = () =>
-        wrappedUseRoutes([
-          {
-            path: 'projects/:projectId/*',
-            element: <ViewsRoutes />,
-          },
-          {
-            path: '*',
-            element: <div>No Match Page</div>,
-          },
-        ]);
-
-      const Routes = () =>
-        wrappedUseRoutes([
-          {
-            path: '/*',
-            element: <ProjectsRoutes />,
-          },
-        ]);
-
-      render(
-        <MemoryRouter initialEntries={['/projects/123/views/456/789']}>
-          <Routes />
-        </MemoryRouter>,
-      );
-
-      expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1);
-      expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/projects/:projectId/views/:viewId/:detailId');
-      expect(mockRootSpan.setAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
-    });
-
-    it('works with descendant wildcard routes - navigation', () => {
-      const client = createMockBrowserClient();
-      setCurrentClient(client);
-
-      client.addIntegration(
-        reactRouterV6BrowserTracingIntegration({
-          useEffect: React.useEffect,
-          useLocation,
-          useNavigationType,
-          createRoutesFromChildren,
-          matchRoutes,
-        }),
-      );
-
-      const wrappedUseRoutes = wrapUseRoutesV6(useRoutes);
-
-      const DetailsRoutes = () =>
-        wrappedUseRoutes([
-          {
-            path: ':detailId',
-            element: <div id="details">Details</div>,
-          },
-        ]);
-
-      const ViewsRoutes = () =>
-        wrappedUseRoutes([
-          {
-            index: true,
-            element: <div id="views">Views</div>,
-          },
-          {
-            path: 'views/:viewId/*',
-            element: <DetailsRoutes />,
-          },
-        ]);
-
-      const ProjectsRoutes = () =>
-        wrappedUseRoutes([
-          {
-            path: 'projects/:projectId/*',
-            element: <ViewsRoutes />,
-          },
-          {
-            path: '*',
-            element: <div>No Match Page</div>,
-          },
-        ]);
-
-      const Routes = () =>
-        wrappedUseRoutes([
-          {
-            index: true,
-            element: <Navigate to="/projects/123/views/456/789" />,
-          },
-          {
-            path: '/*',
-            element: <ProjectsRoutes />,
-          },
-        ]);
-
-      render(
-        <MemoryRouter initialEntries={['/']}>
-          <Routes />
-        </MemoryRouter>,
-      );
-
-      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
-      expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
-        name: '/projects/:projectId/views/:viewId/:detailId',
-        attributes: {
-          [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
-          [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
-          [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
-        },
-      });
-    });
-
     it('does not add double slashes to URLS', () => {
       const client = createMockBrowserClient();
       setCurrentClient(client);