Skip to content

Commit c47669c

Browse files
committed
fix(react): Defer React Router span finalization until lazy routes load
1 parent 9115e19 commit c47669c

File tree

5 files changed

+319
-40
lines changed

5 files changed

+319
-40
lines changed

dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,20 @@ const router = sentryCreateBrowserRouter(
134134
lazyChildren: () => import('./pages/SlowFetchLazyRoutes').then(module => module.slowFetchRoutes),
135135
},
136136
},
137+
{
138+
// Route with wildcard placeholder that gets replaced by lazy-loaded parameterized routes
139+
// This tests that wildcard transaction names get upgraded to parameterized routes
140+
path: '/wildcard-lazy',
141+
children: [
142+
{
143+
path: '*', // Catch-all wildcard - will be matched initially before lazy routes load
144+
element: <>Loading...</>,
145+
},
146+
],
147+
handle: {
148+
lazyChildren: () => import('./pages/WildcardLazyRoutes').then(module => module.wildcardRoutes),
149+
},
150+
},
137151
],
138152
{
139153
async patchRoutesOnNavigation({ matches, patch }: Parameters<PatchRoutesOnNavigationFunction>[0]) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
import { useParams } from 'react-router-dom';
3+
4+
// Simulate slow lazy route loading (500ms delay via top-level await)
5+
await new Promise(resolve => setTimeout(resolve, 500));
6+
7+
function WildcardItem() {
8+
const { id } = useParams();
9+
return <div>Wildcard Item: {id}</div>;
10+
}
11+
12+
export const wildcardRoutes = [
13+
{
14+
path: ':id',
15+
element: <WildcardItem />,
16+
},
17+
];

dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,3 +1228,49 @@ test('Query/hash navigation does not corrupt transaction name', async ({ page })
12281228
const corruptedToRoot = navigationTransactions.filter(t => t.name === '/');
12291229
expect(corruptedToRoot.length).toBe(0);
12301230
});
1231+
1232+
// Regression: Pageload to slow lazy route should get parameterized name even if span ends early
1233+
test('Slow lazy route pageload with early span end still gets parameterized route name (regression)', async ({
1234+
page,
1235+
}) => {
1236+
const transactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
1237+
return (
1238+
!!transactionEvent?.transaction &&
1239+
transactionEvent.contexts?.trace?.op === 'pageload' &&
1240+
(transactionEvent.transaction?.startsWith('/slow-fetch') ?? false)
1241+
);
1242+
});
1243+
1244+
// idleTimeout=300 ends span before 500ms lazy route loads, timeout=1000 waits for lazy routes
1245+
await page.goto('/slow-fetch/123?idleTimeout=300&timeout=1000');
1246+
1247+
const event = await transactionPromise;
1248+
1249+
expect(event.transaction).toBe('/slow-fetch/:id');
1250+
expect(event.type).toBe('transaction');
1251+
expect(event.contexts?.trace?.op).toBe('pageload');
1252+
expect(event.contexts?.trace?.data?.['sentry.source']).toBe('route');
1253+
1254+
const idleSpanFinishReason = event.contexts?.trace?.data?.['sentry.idle_span_finish_reason'];
1255+
expect(['idleTimeout', 'externalFinish']).toContain(idleSpanFinishReason);
1256+
});
1257+
1258+
// Regression: Wildcard route names should be upgraded to parameterized routes when lazy routes load
1259+
test('Wildcard route pageload gets upgraded to parameterized route name (regression)', async ({ page }) => {
1260+
const transactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
1261+
return (
1262+
!!transactionEvent?.transaction &&
1263+
transactionEvent.contexts?.trace?.op === 'pageload' &&
1264+
(transactionEvent.transaction?.startsWith('/wildcard-lazy') ?? false)
1265+
);
1266+
});
1267+
1268+
await page.goto('/wildcard-lazy/456?idleTimeout=300&timeout=1000');
1269+
1270+
const event = await transactionPromise;
1271+
1272+
expect(event.transaction).toBe('/wildcard-lazy/:id');
1273+
expect(event.type).toBe('transaction');
1274+
expect(event.contexts?.trace?.op).toBe('pageload');
1275+
expect(event.contexts?.trace?.data?.['sentry.source']).toBe('route');
1276+
});

0 commit comments

Comments
 (0)