diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx
index e1a97443d756..3fe40df8ece8 100644
--- a/packages/react/src/reactrouter.tsx
+++ b/packages/react/src/reactrouter.tsx
@@ -9,6 +9,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
getActiveSpan,
+ getCurrentScope,
getRootSpan,
spanToJSON,
} from '@sentry/core';
@@ -226,9 +227,13 @@ export function withSentryRouting
, R extends React
const activeRootSpan = getActiveRootSpan();
const WrappedRoute: React.FC
= (props: P) => {
- if (activeRootSpan && props && props.computedMatch && props.computedMatch.isExact) {
- activeRootSpan.updateName(props.computedMatch.path);
- activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
+ if (props && props.computedMatch && props.computedMatch.isExact) {
+ getCurrentScope().setTransactionName(props.computedMatch.path);
+
+ if (activeRootSpan) {
+ activeRootSpan.updateName(props.computedMatch.path);
+ activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
+ }
}
// @ts-expect-error Setting more specific React Component typing for `R` generic above
diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx
index cdf798cb84c7..ab67ba44bb5a 100644
--- a/packages/react/src/reactrouterv6.tsx
+++ b/packages/react/src/reactrouterv6.tsx
@@ -13,6 +13,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
getActiveSpan,
getClient,
+ getCurrentScope,
getRootSpan,
spanToJSON,
} from '@sentry/core';
@@ -198,10 +199,15 @@ function updatePageloadTransaction(
? matches
: (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]);
- if (activeRootSpan && branches) {
+ if (branches) {
const [name, source] = getNormalizedName(routes, location, branches, basename);
- activeRootSpan.updateName(name);
- activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
+
+ getCurrentScope().setTransactionName(name);
+
+ if (activeRootSpan) {
+ activeRootSpan.updateName(name);
+ activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
+ }
}
}
diff --git a/packages/react/test/reactrouterv3.test.tsx b/packages/react/test/reactrouterv3.test.tsx
index 413c9566e71a..c207ead3aab3 100644
--- a/packages/react/test/reactrouterv3.test.tsx
+++ b/packages/react/test/reactrouterv3.test.tsx
@@ -29,7 +29,6 @@ const mockStartBrowserTracingPageLoadSpan = jest.fn();
const mockStartBrowserTracingNavigationSpan = jest.fn();
const mockRootSpan = {
- updateName: jest.fn(),
setAttribute: jest.fn(),
getSpanJSON() {
return { op: 'pageload' };
@@ -115,6 +114,18 @@ describe('browserTracingReactRouterV3', () => {
});
});
+ it("updates the scope's `transactionName` on pageload", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(reactRouterV3BrowserTracingIntegration({ history, routes: instrumentationRoutes, match }));
+
+ client.init();
+ render({routes});
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+ });
+
it('starts a navigation transaction', () => {
const client = createMockBrowserClient();
setCurrentClient(client);
@@ -192,4 +203,23 @@ describe('browserTracingReactRouterV3', () => {
},
});
});
+
+ it("updates the scope's `transactionName` on a navigation", () => {
+ const client = createMockBrowserClient();
+
+ const history = createMemoryHistory();
+ client.addIntegration(reactRouterV3BrowserTracingIntegration({ history, routes: instrumentationRoutes, match }));
+
+ client.init();
+ const { container } = render({routes});
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+
+ act(() => {
+ history.push('/users/123');
+ });
+ expect(container.innerHTML).toContain('123');
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/users/:userid');
+ });
});
diff --git a/packages/react/test/reactrouterv4.test.tsx b/packages/react/test/reactrouterv4.test.tsx
index 5e6a142c11cd..0f331ba26abe 100644
--- a/packages/react/test/reactrouterv4.test.tsx
+++ b/packages/react/test/reactrouterv4.test.tsx
@@ -86,6 +86,18 @@ describe('browserTracingReactRouterV4', () => {
});
});
+ it("updates the scope's `transactionName` on pageload", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ const history = createMemoryHistory();
+ client.addIntegration(reactRouterV4BrowserTracingIntegration({ history }));
+
+ client.init();
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+ });
+
it('starts a navigation transaction', () => {
const client = createMockBrowserClient();
setCurrentClient(client);
@@ -341,4 +353,45 @@ describe('browserTracingReactRouterV4', () => {
},
});
});
+
+ it("updates the scope's `transactionName` on a route change", () => {
+ const routes: RouteConfig[] = [
+ {
+ path: '/organizations/:orgid/v1/:teamid',
+ },
+ { path: '/organizations/:orgid' },
+ { path: '/' },
+ ];
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ const history = createMemoryHistory();
+ client.addIntegration(reactRouterV4BrowserTracingIntegration({ history, routes, matchPath }));
+
+ client.init();
+
+ const SentryRoute = withSentryRouting(Route);
+
+ render(
+
+
+ Team
} />
+ OrgId
} />
+ Home
} />
+
+ ,
+ );
+
+ act(() => {
+ history.push('/organizations/1234/v1/758');
+ });
+
+ expect(getCurrentScope().getScopeData().transactionName).toEqual('/organizations/:orgid/v1/:teamid');
+
+ act(() => {
+ history.push('/organizations/1234');
+ });
+
+ expect(getCurrentScope().getScopeData().transactionName).toEqual('/organizations/:orgid');
+ });
});
diff --git a/packages/react/test/reactrouterv5.test.tsx b/packages/react/test/reactrouterv5.test.tsx
index 7d4939cce522..901745f86b23 100644
--- a/packages/react/test/reactrouterv5.test.tsx
+++ b/packages/react/test/reactrouterv5.test.tsx
@@ -86,6 +86,18 @@ describe('browserTracingReactRouterV5', () => {
});
});
+ it("updates the scope's `transactionName` on pageload", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ const history = createMemoryHistory();
+ client.addIntegration(reactRouterV5BrowserTracingIntegration({ history }));
+
+ client.init();
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+ });
+
it('starts a navigation transaction', () => {
const client = createMockBrowserClient();
setCurrentClient(client);
@@ -341,4 +353,45 @@ describe('browserTracingReactRouterV5', () => {
},
});
});
+
+ it("updates the scope's `transactionName` on a route change", () => {
+ const routes: RouteConfig[] = [
+ {
+ path: '/organizations/:orgid/v1/:teamid',
+ },
+ { path: '/organizations/:orgid' },
+ { path: '/' },
+ ];
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ const history = createMemoryHistory();
+ client.addIntegration(reactRouterV5BrowserTracingIntegration({ history, routes, matchPath }));
+
+ client.init();
+
+ const SentryRoute = withSentryRouting(Route);
+
+ render(
+
+
+ Team
} />
+ OrgId
} />
+ Home
} />
+
+ ,
+ );
+
+ act(() => {
+ history.push('/organizations/1234/v1/758');
+ });
+
+ expect(getCurrentScope().getScopeData().transactionName).toBe('/organizations/:orgid/v1/:teamid');
+
+ act(() => {
+ history.push('/organizations/1234');
+ });
+
+ expect(getCurrentScope().getScopeData().transactionName).toBe('/organizations/:orgid');
+ });
});
diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx
index 34fe85b6bfc9..b2410e3bfad9 100644
--- a/packages/react/test/reactrouterv6.4.test.tsx
+++ b/packages/react/test/reactrouterv6.4.test.tsx
@@ -122,6 +122,39 @@ describe('reactRouterV6BrowserTracingIntegration (v6.4)', () => {
});
});
+ it("updates the scope's `transactionName` on a pageload", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(
+ reactRouterV6BrowserTracingIntegration({
+ useEffect: React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ }),
+ );
+ const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction);
+
+ const router = sentryCreateBrowserRouter(
+ [
+ {
+ path: '/',
+ element:
TEST
,
+ },
+ ],
+ {
+ initialEntries: ['/'],
+ },
+ );
+
+ // @ts-expect-error router is fine
+ render();
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+ });
+
it('starts a navigation transaction', () => {
const client = createMockBrowserClient();
setCurrentClient(client);
@@ -590,5 +623,42 @@ describe('reactRouterV6BrowserTracingIntegration (v6.4)', () => {
},
});
});
+
+ it("updates the scope's `transactionName` on a navigation", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(
+ reactRouterV6BrowserTracingIntegration({
+ useEffect: React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ }),
+ );
+ const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction);
+
+ const router = sentryCreateBrowserRouter(
+ [
+ {
+ path: '/',
+ element: ,
+ },
+ {
+ path: 'about',
+ element: About
,
+ },
+ ],
+ {
+ initialEntries: ['/'],
+ },
+ );
+
+ // @ts-expect-error router is fine
+ render();
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/about');
+ });
});
});
diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx
index c86f55ccbd73..131161856214 100644
--- a/packages/react/test/reactrouterv6.test.tsx
+++ b/packages/react/test/reactrouterv6.test.tsx
@@ -114,6 +114,32 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
});
});
+ it("updates the scope's `transactionName` on a pageload", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(
+ reactRouterV6BrowserTracingIntegration({
+ useEffect: React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ }),
+ );
+ const SentryRoutes = withSentryReactRouterV6Routing(Routes);
+
+ render(
+
+
+ Home} />
+
+ ,
+ );
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+ });
+
it('skips pageload transaction with `instrumentPageLoad: false`', () => {
const client = createMockBrowserClient();
setCurrentClient(client);
@@ -364,6 +390,35 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
},
});
});
+
+ it("updates the scope's `transactionName` on a navigation", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(
+ reactRouterV6BrowserTracingIntegration({
+ useEffect: React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ }),
+ );
+ const SentryRoutes = withSentryReactRouterV6Routing(Routes);
+
+ render(
+
+
+ About}>
+ page} />
+
+ } />
+
+ ,
+ );
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toBe('/about/:page');
+ });
});
describe('wrapUseRoutes', () => {
@@ -408,6 +463,39 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
});
});
+ it("updates the scope's `transactionName` on a pageload", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(
+ reactRouterV6BrowserTracingIntegration({
+ useEffect: React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ }),
+ );
+
+ const wrappedUseRoutes = wrapUseRoutes(useRoutes);
+
+ const Routes = () =>
+ wrappedUseRoutes([
+ {
+ path: '/',
+ element: Home
,
+ },
+ ]);
+
+ render(
+
+
+ ,
+ );
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
+ });
+
it('skips pageload transaction with `instrumentPageLoad: false`', () => {
const client = createMockBrowserClient();
setCurrentClient(client);
@@ -875,5 +963,41 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/tests/:testId/*');
expect(mockRootSpan.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
});
+
+ it("updates the scope's `transactionName` on a navigation", () => {
+ const client = createMockBrowserClient();
+ setCurrentClient(client);
+
+ client.addIntegration(
+ reactRouterV6BrowserTracingIntegration({
+ useEffect: React.useEffect,
+ useLocation,
+ useNavigationType,
+ createRoutesFromChildren,
+ matchRoutes,
+ }),
+ );
+ const wrappedUseRoutes = wrapUseRoutes(useRoutes);
+
+ const Routes = () =>
+ wrappedUseRoutes([
+ {
+ path: '/',
+ element: ,
+ },
+ {
+ path: '/about',
+ element: About
,
+ },
+ ]);
+
+ render(
+
+
+ ,
+ );
+
+ expect(getCurrentScope().getScopeData()?.transactionName).toBe('/about');
+ });
});
});