From e37632a4e31afa527b7c45bc44309d844592fb53 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 9 Mar 2026 11:03:58 +0100 Subject: [PATCH 1/3] bot detection --- .../src/tracing/browserTracingIntegration.ts | 27 ++++++++ .../tracing/browserTracingIntegration.test.ts | 62 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index c71acf106258..2c5426aab783 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -54,6 +54,22 @@ import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from export const BROWSER_TRACING_INTEGRATION_ID = 'BrowserTracing'; +/** + * We don't want to start a bunch of idle timers and PerformanceObservers + * for web crawlers, as they may prevent the page from being seen as "idle" + * by the crawler's rendering engine (e.g. Googlebot's headless Chromium). + */ +const BOT_USER_AGENT_RE = + /Googlebot|Google-InspectionTool|Storebot-Google|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Facebot|facebookexternalhit|LinkedInBot|Twitterbot|Applebot/i; + +function _isBotUserAgent(): boolean { + const nav = WINDOW.navigator as Navigator | undefined; + if (!nav?.userAgent) { + return false; + } + return BOT_USER_AGENT_RE.test(nav.userAgent); +} + interface RouteInfo { name: string | undefined; source: TransactionSource | undefined; @@ -384,6 +400,8 @@ export const browserTracingIntegration = ((options: Partial void); let lastInteractionTimestamp: number | undefined; @@ -484,6 +502,11 @@ export const browserTracingIntegration = ((options: Partial { Object.defineProperty(WINDOW, 'history', { value: originalGlobalHistory }); }); + describe('bot user agent detection', () => { + let originalNavigator: Navigator; + + beforeEach(() => { + originalNavigator = WINDOW.navigator; + }); + + afterEach(() => { + Object.defineProperty(WINDOW, 'navigator', { value: originalNavigator, writable: true, configurable: true }); + }); + + function setUserAgent(ua: string): void { + Object.defineProperty(WINDOW, 'navigator', { + value: { userAgent: ua }, + writable: true, + configurable: true, + }); + } + + it.each([ + 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', + 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/W.X.Y.Z Safari/537.36', + 'Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)', + 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)', + 'LinkedInBot/1.0 (compatible; Mozilla/5.0)', + 'Twitterbot/1.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15 (Applebot/0.1)', + 'Mozilla/5.0 (compatible; Google-InspectionTool/1.0)', + ])('skips tracing setup for bot user agent: %s', ua => { + setUserAgent(ua); + + const client = new BrowserClient( + getDefaultBrowserClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration()], + }), + ); + setCurrentClient(client); + client.init(); + + expect(getActiveSpan()).toBeUndefined(); + }); + + it('does not skip tracing setup for normal user agents', () => { + setUserAgent( + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + ); + + const client = new BrowserClient( + getDefaultBrowserClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration()], + }), + ); + setCurrentClient(client); + client.init(); + + expect(getActiveSpan()).toBeDefined(); + }); + }); + it('works with tracing enabled', () => { const client = new BrowserClient( getDefaultBrowserClientOptions({ From daa0c5f0a40550847424b499e098c1c48540fc9d Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 9 Mar 2026 11:20:52 +0100 Subject: [PATCH 2/3] chore: Update size limits for bot detection in browserTracingIntegration Co-Authored-By: Claude Opus 4.6 --- .size-limit.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 38a83445d021..a42719ba096b 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -82,7 +82,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '86 KB', + limit: '87 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', @@ -255,7 +255,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '131 KB', + limit: '132 KB', }, { name: 'CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed', @@ -269,7 +269,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.min.js'), gzip: false, brotli: false, - limit: '245 KB', + limit: '246 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed', @@ -308,7 +308,7 @@ module.exports = [ import: createImport('init'), ignore: ['$app/stores'], gzip: true, - limit: '43 KB', + limit: '44 KB', }, // Node-Core SDK (ESM) { From 201152d519d154ebbbc055e8c895d98bb51f1893 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 9 Mar 2026 14:24:07 +0100 Subject: [PATCH 3/3] fix(nextjs): Pre-warm module cache in conflictingDebugOptions test to prevent CI timeout The first dynamic import after vi.resetModules() needs to compile the entire SDK module graph from scratch, which can hang on slower CI runners. Pre-importing the modules in beforeAll warms the V8 compilation cache. Co-Authored-By: Claude Opus 4.6 --- .../nextjs/test/config/conflictingDebugOptions.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/test/config/conflictingDebugOptions.test.ts b/packages/nextjs/test/config/conflictingDebugOptions.test.ts index 8c0920382c4a..5e7a46997b2b 100644 --- a/packages/nextjs/test/config/conflictingDebugOptions.test.ts +++ b/packages/nextjs/test/config/conflictingDebugOptions.test.ts @@ -20,7 +20,15 @@ describe('debug: true + removeDebugLogging warning', () => { let originalLocation: unknown; let originalAddEventListener: unknown; - beforeAll(() => { + beforeAll(async () => { + // Pre-warm V8 compilation cache for the large SDK module graphs. + // Without this, the first dynamic import after vi.resetModules() can hang + // because vitest needs to compile the entire module graph from scratch. + await import('../../src/client/index.js'); + await import('../../src/server/index.js'); + await import('../../src/edge/index.js'); + vi.resetModules(); + dom = new JSDOM('', { url: 'https://example.com/' }); originalDocument = (globalThis as any).document;