Skip to content

feat(browser): Detect redirects when emitting navigation spans #16324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

mydea
Copy link
Member

@mydea mydea commented May 19, 2025

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.

@mydea mydea requested review from bcoe, Lms24 and s1gr1d May 19, 2025 09:21
@mydea mydea self-assigned this May 19, 2025
Copy link
Contributor

github-actions bot commented May 19, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 23.99 kB - -
@sentry/browser - with treeshaking flags 23.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/react 25.76 kB - -
⛔️ @sentry/react (incl. Tracing) (max: 41 kB) 41.02 kB +0.58% +236 B 🔺
@sentry/vue 28.36 kB - -
@sentry/vue (incl. Tracing) 40.89 kB +0.59% +239 B 🔺
@sentry/svelte 24.01 kB - -
CDN Bundle 25.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 - uncompressed 74.48 kB - -
CDN Bundle (incl. Tracing) - uncompressed 115.8 kB +0.44% +502 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 229.86 kB +0.22% +502 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 242.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/node 150.76 kB - -
@sentry/node - without tracing 98.52 kB - -
@sentry/aws-serverless 124.27 kB -0.01% -1 B 🔽

View base workflow run

Copy link

codecov bot commented May 19, 2025

❌ 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.

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

@edwardgou-sentry edwardgou-sentry left a comment

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

Copy link
Member

@Lms24 Lms24 left a comment

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.

@mydea mydea 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;

@mydea mydea 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 GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Distinguish redirects from user-initiated nagivations
5 participants