Skip to content

feat(blazor): Add navigation breadcrumbs for Blazor WebAssembly#4907

Merged
bruno-garcia merged 8 commits intomainfrom
feat/blazor-wasm-nav-breadcrumbs
Feb 13, 2026
Merged

feat(blazor): Add navigation breadcrumbs for Blazor WebAssembly#4907
bruno-garcia merged 8 commits intomainfrom
feat/blazor-wasm-nav-breadcrumbs

Conversation

@bruno-garcia
Copy link
Member

Summary

  • Automatically create navigation breadcrumbs when users navigate between pages in a Blazor WebAssembly app
  • Breadcrumbs use type: "navigation", category: "navigation", and data: { from, to } with relative paths — matching the JS SDK behavior so the Sentry UI renders the navigation icon and from/to display correctly
  • Keeps scope.Request.Url updated with the current route

Closes #4906

Implementation

  • BlazorWasmOptionsSetup (IConfigureOptions<SentryBlazorOptions>) injects NavigationManager, subscribes to LocationChanged, and creates breadcrumbs on each navigation
  • Registered in DI via WebAssemblyHostBuilderExtensions.UseSentry()
  • Uses NavigationManager.ToBaseRelativePath() to convert absolute URLs to relative paths (prepending / to match JS SDK format)

Test plan

  • 7 unit tests covering:
    • Initial scope.Request.Url is set correctly (root and with path)
    • Breadcrumb has correct type and category
    • from/to values are relative paths
    • scope.Request.Url is updated on navigation
    • Multiple navigations track from correctly (previous → current)
    • Navigation from a non-root initial path tracks correct from
  • All tests pass on net8.0, net9.0, net10.0

🤖 Generated with Claude Code

Subscribe to NavigationManager.LocationChanged to automatically create
navigation breadcrumbs with type/category "navigation" and from/to
relative paths, matching the JS SDK behavior. Also keeps
scope.Request.Url updated with the current route.

Closes #4906

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Features ✨

  • feat(blazor): Add navigation breadcrumbs for Blazor WebAssembly by bruno-garcia in #4907

Dependencies ⬆️

Deps

  • chore(deps): update Java SDK to v8.32.0 by github-actions in #4843
  • chore(deps): add protobuf-javalite 3.25.8 dependency on Android by github-actions in #4843
  • chore(deps): Bumped Xamarin.AndroidX.Lifecycle.Common.Java8 and CommunityToolkit.Mvvm to 2.2.20 and 8.4.0 by jamescrosswell in #4876

Other

  • ci: Skip device tests for non-mobile PRs by bruno-garcia in #4909

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against a8333c7

@codecov
Copy link

codecov bot commented Feb 10, 2026

Codecov Report

❌ Patch coverage is 87.80488% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.85%. Comparing base (9a88d5e) to head (a8333c7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...zor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs 90.00% 3 Missing and 1 partial ⚠️
...or.WebAssembly/WebAssemblyHostBuilderExtensions.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4907      +/-   ##
==========================================
- Coverage   73.85%   73.85%   -0.01%     
==========================================
  Files         494      496       +2     
  Lines       17868    17921      +53     
  Branches     3509     3510       +1     
==========================================
+ Hits        13197    13236      +39     
- Misses       3815     3824       +9     
- Partials      856      861       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

bruno-garcia and others added 2 commits February 9, 2026 23:11
…empty message

- BlazorWasmOptionsSetup now takes IHub via an internal constructor
  (defaults to HubAdapter.Instance in production). This allows tests to
  inject a mock hub instead of relying on SentrySdk global state.
- Use the internal Breadcrumb constructor with message: null instead of
  sending message: "" (matching JS SDK which omits the message property).
- Add InternalsVisibleTo from Sentry to the Blazor WASM project.
- Rewrite tests to use NSubstitute IHub + real Scope (following MAUI
  test patterns) instead of SentrySdk.Init().
- Add test verifying breadcrumb message is null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the test project to the solution and regenerate all .slnf files
so CI test-solution-filters passes. Add changelog entry for the
navigation breadcrumbs feature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bruno-garcia bruno-garcia marked this pull request as ready for review February 10, 2026 05:13
Copilot AI review requested due to automatic review settings February 10, 2026 05:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds automatic navigation breadcrumb tracking to the Blazor WebAssembly integration so Sentry captures SPA route transitions similarly to the JavaScript SDK, and keeps scope.Request.Url aligned with the current route.

Changes:

  • Introduces BlazorWasmOptionsSetup to subscribe to NavigationManager.LocationChanged, emit type/category: "navigation" breadcrumbs with { from, to }, and update scope.Request.Url.
  • Registers the options setup in WebAssemblyHostBuilderExtensions.UseSentry().
  • Adds a new WASM test project with unit tests validating breadcrumb shape and URL updates.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Sentry.AspNetCore.Blazor.WebAssembly/Internal/BlazorWasmOptionsSetup.cs Implements LocationChanged subscription, breadcrumb creation, and scope.Request.Url updates.
src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs Registers the options setup in DI when UseSentry() is called.
test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/* Adds unit tests and a FakeNavigationManager for navigation simulation.
src/Sentry/Sentry.csproj Grants internals access to the Blazor WASM assembly (needed for internal breadcrumb ctor usage).
CHANGELOG.md Documents the new Blazor WASM navigation breadcrumb feature.
*.slnf, Sentry.sln, .generated.NoMobile.sln Includes the new test project in solution/filters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Remove unused using and hidden NavigateTo method in FakeNavigationManager
- Add ArgumentNullException.ThrowIfNull(options) for consistency
- Add _initialized guard to prevent duplicate event handlers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bruno-garcia
Copy link
Member Author

Review Comments Summary

# Author Comment Outcome
1 @bruno-garcia Tests should use Hub instances, not static state Fixed (previous commit) — added internal IHub constructor, tests use NSubstitute mock
2 @bruno-garcia Should not send message: "" in breadcrumb Fixed (previous commit) — uses internal Breadcrumb constructor with message: null
3 Copilot Unused using Microsoft.AspNetCore.Components.Routing; Fixed — removed
4 Copilot FakeNavigationManager.NavigateTo hides base member (CS0108) Fixed — removed the public method; tests now call the inherited NavigateTo which routes through NavigateToCore
5 Copilot Missing ArgumentNullException.ThrowIfNull(options) Fixed — added for consistency with other IConfigureOptions implementations
6 cursor[bot] Duplicate event handlers on repeated Configure calls Fixed — added _initialized boolean guard
7 cursor[bot] IConfigureOptions misused for side-effects No change — intentional design; same pattern as SentryMauiOptionsSetup. IConfigureOptions is the standard hook for initialization requiring DI services
8 cursor[bot] Initial scope URL may not be set due to init ordering No change — best-effort by design. The LocationChanged handler (core feature) works correctly; initial URL is set on first navigation regardless

All fixes are in commit 11ada43.

Copy link
Collaborator

@jamescrosswell jamescrosswell left a comment

Choose a reason for hiding this comment

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

The PR looks good. I had a question about thread safety on the IConfigureOptions... maybe this is a non-concern in WASM apps though.

Copy link
Collaborator

Choose a reason for hiding this comment

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

FYI: We don't strictly require a changelog entry anymore - it should get generated automatically from the commit description since #4896

Copy link
Member Author

Choose a reason for hiding this comment

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

Do I need to remove it? it's already there

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nah you can leave it in... it would be generated automatically for the PR (from the title) when making a release otherwise. Just FYI that you don't need to add this anymore.

{
return;
}
_initialized = true;
Copy link
Collaborator

@jamescrosswell jamescrosswell Feb 12, 2026

Choose a reason for hiding this comment

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

Is there a potential race condition here? Does this need to be thread safe?

I think IOptions<T> gets evaluated lazily the first time it's requested... so if multiple requests hit the server concurrently after it starts, you could get two threads checking it _initialized is true, seeing that they're not and then setting up duplicate event handlers (resulting in duplicate breadcrumbs).

Better to use Interlocked.Exchange?

But if this is all compiling down to WASM, maybe there are no threads? There are none in javascript - I assume WASM is the same? @Flash0ver this sounds like your area of expertise...

edit: Turns out .NET does have experimental support for threading in WASM... maybe we use Interlocked here just to be on the safe side then (even though it might not be strictly required)? I think for navigation events in particular, it's extremely unlikely to cause problems, so not pushing super hard for this.

Copy link
Member Author

Choose a reason for hiding this comment

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

This runs single threaded, since it runs on the main thread when the app starts up

Copy link
Member

@Flash0ver Flash0ver Feb 13, 2026

Choose a reason for hiding this comment

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

I'm a bit uncertain if this is (or may be) an issue,
but considering that the change using our InterlockedBoolean is both cheap in runtime and maintenance (would result in less code, actually),
I vote for a thread-safe guard here using our internal InterlockedBoolean.

@bruno-garcia bruno-garcia enabled auto-merge (squash) February 13, 2026 04:03
var from = ToRelativePath(previousUrl);
var to = ToRelativePath(args.Location);

_hub.AddBreadcrumb(
Copy link
Member

@Flash0ver Flash0ver Feb 13, 2026

Choose a reason for hiding this comment

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

Comment on lines +58 to +61
_hub.ConfigureScope(scope =>
{
scope.Request.Url = to;
});
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: we could avoid the closure here, as to is already a string-ified relative path, by passing it as "state"

blazorOptions.IsGlobalModeEnabled = true;
});

builder.Services.AddSingleton<IConfigureOptions<SentryBlazorOptions>, BlazorWasmOptionsSetup>();
Copy link
Member

Choose a reason for hiding this comment

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

issue: with that change, Sentry.Samples.AspNetCore.Blazor.Wasm doesn't fully initialize any longer and just hangs

Copy link
Member

@Flash0ver Flash0ver Feb 13, 2026

Choose a reason for hiding this comment

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

however, the Unit Tests and Playwright Tests (#4908) do pass

Copy link
Member

@Flash0ver Flash0ver left a comment

Choose a reason for hiding this comment

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

This may have broken the sample Sentry.AspNetCore.Blazor.WebAssembly.
But Unit Tests and Playwright Tests (#4908) pass.
Could also be a local issue on my machine / setup.

@bruno-garcia bruno-garcia merged commit 14ec84f into main Feb 13, 2026
39 checks passed
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.

Blazor WebAssembly SDK: Next-Gen Feature Expansion

3 participants