Skip to content

Commit 28cabee

Browse files
committed
fix(react): Ignore React 19.2+ component render measure entries
1 parent d1646c8 commit 28cabee

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

packages/browser-utils/src/metrics/browserMetrics.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,24 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
425425
_measurements = {};
426426
}
427427

428+
/**
429+
* React 19.2+ creates performance.measure entries for component renders.
430+
* We can identify them by the `detail.devtools.track` property being set to 'Components ⚛'.
431+
* see: https://react.dev/reference/dev-tools/react-performance-tracks
432+
* see: https://github.com/facebook/react/blob/06fcc8f380c6a905c7bc18d94453f623cf8cbc81/packages/react-reconciler/src/ReactFiberPerformanceTrack.js#L454-L473
433+
*/
434+
function isReact19MeasureEntry(entry: PerformanceEntry | null): boolean | void {
435+
if (entry?.entryType !== 'measure') {
436+
return;
437+
}
438+
try {
439+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
440+
return (entry as PerformanceMeasure).detail.devtools.track === 'Components ⚛';
441+
} catch {
442+
return;
443+
}
444+
}
445+
428446
/**
429447
* Create measure related spans.
430448
* Exported only for tests.
@@ -437,6 +455,10 @@ export function _addMeasureSpans(
437455
timeOrigin: number,
438456
ignorePerformanceApiSpans: AddPerformanceEntriesOptions['ignorePerformanceApiSpans'],
439457
): void {
458+
if (isReact19MeasureEntry(entry)) {
459+
return;
460+
}
461+
440462
if (
441463
['mark', 'measure'].includes(entry.entryType) &&
442464
stringMatchesSomePattern(entry.name, ignorePerformanceApiSpans)
@@ -445,6 +467,7 @@ export function _addMeasureSpans(
445467
}
446468

447469
const navEntry = getNavigationEntry(false);
470+
448471
const requestTime = msToSec(navEntry ? navEntry.requestStart : 0);
449472
// Because performance.measure accepts arbitrary timestamps it can produce
450473
// spans that happen before the browser even makes a request for the page.

packages/browser-utils/test/browser/browserMetrics.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,75 @@ describe('_addMeasureSpans', () => {
186186
]),
187187
);
188188
});
189+
190+
it('ignores React 19.2+ measure spans', () => {
191+
const pageloadSpan = new SentrySpan({ op: 'pageload', name: '/', sampled: true });
192+
const spans: Span[] = [];
193+
194+
getClient()?.on('spanEnd', span => {
195+
spans.push(span);
196+
});
197+
198+
const entries: PerformanceMeasure[] = [
199+
{
200+
entryType: 'measure',
201+
name: '\u200bLayout',
202+
duration: 0.3,
203+
startTime: 12,
204+
detail: {
205+
devtools: {
206+
track: 'Components ⚛',
207+
},
208+
},
209+
toJSON: () => ({ foo: 'bar' }),
210+
},
211+
{
212+
entryType: 'measure',
213+
name: '\u200bButton',
214+
duration: 0.1,
215+
startTime: 13,
216+
detail: {
217+
devtools: {
218+
track: 'Components ⚛',
219+
},
220+
},
221+
toJSON: () => ({}),
222+
},
223+
{
224+
entryType: 'measure',
225+
name: 'Unmount',
226+
duration: 0.1,
227+
startTime: 14,
228+
detail: {
229+
devtools: {
230+
track: 'Components ⚛',
231+
},
232+
},
233+
toJSON: () => ({}),
234+
},
235+
{
236+
entryType: 'measure',
237+
name: 'my-measurement',
238+
duration: 0,
239+
startTime: 12,
240+
detail: null,
241+
toJSON: () => ({}),
242+
},
243+
];
244+
245+
const timeOrigin = 100;
246+
const startTime = 23;
247+
const duration = 356;
248+
249+
entries.forEach(e => {
250+
_addMeasureSpans(pageloadSpan, e, startTime, duration, timeOrigin, []);
251+
});
252+
253+
expect(spans).toHaveLength(1);
254+
expect(spans.map(spanToJSON)).toEqual(
255+
expect.arrayContaining([expect.objectContaining({ description: 'my-measurement', op: 'measure' })]),
256+
);
257+
});
189258
});
190259

191260
describe('_addResourceSpans', () => {

0 commit comments

Comments
 (0)