Conversation

mydea

Closes #15286

This PR adds a new option to browserTracingIntegration, detectRedirects, which is enabled by default. If this is enabled, the integration will try to detect if a navigation is actually a redirect based on a simple heuristic, and in this case, will not end the ongoing pageload/navigation, but instead let it run and create a navigation.redirect zero-duration span instead.

An example trace for this would be: https://sentry-sdks.sentry.io/explore/discover/trace/95280de69dc844448d39de7458eab527/?dataset=transactions&eventId=8a1150fd1dc846e4ac8420ccf03ad0ee&field=title&field=project&field=user.display&field=timestamp&name=All%20Errors&project=4504956726345728&query=&queryDataset=transaction-like&sort=-timestamp&source=discover&statsPeriod=5m&timestamp=1747646096&yAxis=count%28%29
image

Where the respective index route that triggered this has this code:

setTimeout(() => {
  window.history.pushState({}, "", "/test-sub-page");
  fetch('https://example.com')
}, 100);

The used heuristic is:

  • If the ongoing pageload/navigation was started less than 300ms ago...
  • ... and no click has happened in this time...
  • ... then we consider the navigation a redirect

this limit was chosen somewhat arbitrarily, open for other suggestions too.

While this logic will not be 100% bullet proof, it should be reliable enough and likely better than what we have today. Users can opt-out of this logic via browserTracingIntegration({ detectRedirects: false }), if needed.

@mydeamydea requested review from bcoe, Lms24 and s1gr1d May 19, 2025 09:21
@mydeamydea self-assigned this May 19, 2025
@github-actionsGitHub Actions

size-limit report 📦

PathSize% ChangeChange
@sentry/browser23.99 kB--
@sentry/browser - with treeshaking flags23.76 kB--
⛔️ @sentry/browser (incl. Tracing) (max: 39 kB)39.03 kB+0.61%+236 B 🔺
⛔️ @sentry/browser (incl. Tracing, Replay) (max: 77 kB)77.14 kB+0.3%+230 B 🔺
⛔️ @sentry/browser (incl. Tracing, Replay) - with treeshaking flags (max: 70.1 kB)70.2 kB+0.28%+193 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)81.91 kB+0.29%+232 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)94 kB+0.27%+249 B 🔺
@sentry/browser (incl. Feedback)40.73 kB--
@sentry/browser (incl. sendFeedback)28.7 kB--
@sentry/browser (incl. FeedbackAsync)33.59 kB--
@sentry/react25.76 kB--
⛔️ @sentry/react (incl. Tracing) (max: 41 kB)41.02 kB+0.58%+236 B 🔺
@sentry/vue28.36 kB--
@sentry/vue (incl. Tracing)40.89 kB+0.59%+239 B 🔺
@sentry/svelte24.01 kB--
CDN Bundle25.48 kB--
⛔️ CDN Bundle (incl. Tracing) (max: 39 kB)39.16 kB+0.52%+202 B 🔺
CDN Bundle (incl. Tracing, Replay)75.01 kB+0.27%+198 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)80.44 kB+0.26%+203 B 🔺
CDN Bundle - uncompressed74.48 kB--
CDN Bundle (incl. Tracing) - uncompressed115.8 kB+0.44%+502 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed229.86 kB+0.22%+502 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed242.68 kB+0.21%+502 B 🔺
@sentry/nextjs (client)42.67 kB+0.55%+233 B 🔺
@sentry/sveltekit (client)39.51 kB+0.6%+233 B 🔺
@sentry/node150.76 kB--
@sentry/node - without tracing98.52 kB--
@sentry/aws-serverless124.27 kB-0.01%-1 B 🔽

View base workflow run

@codecovCodecov

❌ Unsupported file format

Upload processing failed due to unsupported file format. Please review the parser error message:

Error parsing JUnit XML in /home/runner/work/sentry-javascript/sentry-javascript/packages/solidstart/vitest.junit.xml at 18:17

Caused by:
    RuntimeError: Error parsing XML
    
    Caused by:
        0: ill-formed document: expected `</testsuites>`, but `</testsuite>` was found
        1: expected `</testsuites>`, but `</testsuite>` was found

For more help, visit our troubleshooting guide.

@mydeamydea force-pushed the fn/detect-pageload-redirects branch 2 times, most recently from ccbd697 to eb3c0bc Compare May 23, 2025 07:22
@mydeamydea marked this pull request as ready for review May 23, 2025 07:22
@mydeamydea force-pushed the fn/detect-pageload-redirects branch from eb3c0bc to cb8e92e Compare May 26, 2025 11:22
@Lms24Lms24 requested a review from edwardgou-sentry June 5, 2025 18:25

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me! There aren't many product areas in performance that specifically rely on navigations so I think this should be fine (and I think we'd consider surfacing redirects in those areas a bug anyways).

@@ -371,6 +396,10 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
startTrackingInteractions();
}

if (detectRedirects && optionalWindowDocument) {
addEventListener('click', () => (lastClickTimestamp = timestampInSeconds()), { capture: true, passive: true });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there other events, such as key presses, that could indicate a user manually navigating?

Copy link
Member

@Lms24 Lms24 Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, keypress might also be a good candidate, agreed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 also looking at keypress

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late review, but LGTM! I think we probably need to widen the timespan a bit because 300ms feel a bit fast to me (thinking of the endless redirects I get when doing SSO or stuff like this). But maybe it's good enough for now. I'd say its something we adjust on a per-feedback basis.

@@ -371,6 +396,10 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
startTrackingInteractions();
}

if (detectRedirects && optionalWindowDocument) {
addEventListener('click', () => (lastClickTimestamp = timestampInSeconds()), { capture: true, passive: true });
Copy link
Member

@Lms24 Lms24 Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, keypress might also be a good candidate, agreed.

@mydeamydea force-pushed the fn/detect-pageload-redirects branch from 19d02d3 to 67791e9 Compare June 17, 2025 10:27
startBrowserTracingNavigationSpan(
client,
{
name: parsed?.pathname || WINDOW.location.pathname,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit(ish): It looks like we are only using this variable once.
Is there any reason to declare it at all? 🙂

Suggested change
name: parsed?.pathname || WINDOW.location.pathname,
name: parseStringToURLObject(to)?.pathname || WINDOW.location.pathname,

return false;
}

return true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is nothing between the condition and the default return, I think we could just set this condition as a final return value:

Suggested change
return true;
return !(lastInteractionTimestamp && now - lastInteractionTimestamp <= REDIRECT_THRESHOLD);

removing the above (L757-L759):

if (lastInteractionTimestamp && now - lastInteractionTimestamp <= REDIRECT_THRESHOLD) {
    return false;
  }


// More than 300ms since last navigation/pageload span?
// --> never consider this a redirect
const startTimestamp = spanData.start_timestamp;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as my other comment we are only using spanData once it looks like. 🙂

Suggested change
const startTimestamp = spanData.start_timestamp;
const startTimestamp = spanToJSON(activeSpan).start_timestamp;

@mydeamydea force-pushed the fn/detect-pageload-redirects branch from 67791e9 to e2018b5 Compare June 18, 2025 07:52
Sign up for free to join this conversation on . Already have an account? Sign in to comment
None yet
None yet

Successfully merging this pull request may close these issues.

Distinguish redirects from user-initiated nagivations