Skip to main content

Built-in Navigation

Overview

The built-in navigation feature provides Redux-style navigation state management for Blazor applications. It tracks navigation history in the store, allowing components to react to route changes and enabling programmatic navigation through dispatched actions.

Use this page to look up the built-in navigation surface, its actions, and its state model.

Minimum Setup

1. Register the Feature

// Program.cs
IReservoirBuilder reservoir = builder.AddReservoir();
reservoir.AddReservoirBlazorBuiltIns(); // Registers navigation + lifecycle

Or register navigation only:

IReservoirBuilder reservoir = builder.AddReservoir();
reservoir.AddBuiltInNavigation();

2. Add the Navigation Provider

Add the ReservoirNavigationProvider component to your App.razor:

@using Mississippi.Reservoir.Client.BuiltIn.Components
<ReservoirNavigationProvider />

<Router AppAssembly="@typeof(App).Assembly">
<!-- ... -->
</Router>

This component subscribes to Blazor's NavigationManager.LocationChanged event and dispatches LocationChangedAction automatically.

3. Navigate via Actions

public class MyComponent : StoreComponent
{
private void HandleNavigation()
{
// Navigate to a new page (pushes history entry)
Dispatch(new NavigateAction("/dashboard"));
}
}

How It Works

  1. User dispatches a navigation action (e.g., NavigateAction)
  2. NavigationEffect handles the action by calling NavigationManager.NavigateTo()
  3. Browser navigation occurs and Blazor fires LocationChanged event
  4. ReservoirNavigationProvider receives the event and dispatches LocationChangedAction
  5. Reducer updates NavigationState with the new URI

Navigates to a new URI, pushing a new entry onto the browser history stack.

// Basic navigation
Dispatch(new NavigateAction("/products"));

// Force full page reload
Dispatch(new NavigateAction("/products", ForceLoad: true));
ParameterTypeDefaultDescription
UristringrequiredThe URI to navigate to (relative or same-origin absolute)
ForceLoadboolfalseIf true, bypasses client-side routing and forces server reload

ReplaceRouteAction

Replaces the current URI in browser history instead of pushing a new entry.

// Replace current history entry (back button won't return here)
Dispatch(new ReplaceRouteAction("/checkout/complete"));

// Common use: redirect after form submission
Dispatch(new ReplaceRouteAction("/success", ForceLoad: false));

Use cases:

  • Redirecting after form submission without allowing "back" to the form
  • Updating query parameters for filtering without polluting history
  • Correcting a URL after initial navigation

SetQueryParamsAction

Updates query string parameters on the current URI.

// Add or update parameters
Dispatch(new SetQueryParamsAction(
new Dictionary<string, object?>
{
["page"] = 2,
["sort"] = "name"
}));

// Remove a parameter by setting to null
Dispatch(new SetQueryParamsAction(
new Dictionary<string, object?>
{
["filter"] = null // Removes 'filter' from query string
}));

// Push to history instead of replace
Dispatch(new SetQueryParamsAction(parameters, ReplaceHistory: false));
ParameterTypeDefaultDescription
ParametersIReadOnlyDictionary<string, object?>requiredQuery parameters to set (null removes)
ReplaceHistorybooltrueIf true, replaces history entry

ScrollToAnchorAction

Scrolls to a named anchor element on the current page.

// Scroll to element with id="section-2"
Dispatch(new ScrollToAnchorAction("section-2"));

// Replace history entry while scrolling
Dispatch(new ScrollToAnchorAction("section-2", ReplaceHistory: true));

This uses Blazor's built-in fragment navigation-no JavaScript interop required.

LocationChangedAction

Dispatched automatically by ReservoirNavigationProvider when navigation completes. You typically don't dispatch this yourself, but you can listen for it in effects.

// In an effect - react to any navigation
public bool CanHandle(IAction action) => action is LocationChangedAction;

public async IAsyncEnumerable<IAction> HandleAsync(
IAction action,
NavigationState currentState,
CancellationToken cancellationToken)
{
if (action is LocationChangedAction { Location: var uri })
{
// Track page view, log analytics, etc.
await TrackPageViewAsync(uri);
}
yield break;
}

The NavigationState feature state tracks navigation information:

public sealed record NavigationState : IFeatureState
{
public static string FeatureKey => "reservoir:navigation";

public string? CurrentUri { get; init; }
public string? PreviousUri { get; init; }
public bool IsNavigationIntercepted { get; init; }
public int NavigationCount { get; init; }
}
PropertyDescription
CurrentUriThe current absolute URI
PreviousUriThe previous URI before the last navigation
IsNavigationInterceptedTrue if Blazor intercepted navigation from a link click
NavigationCountNumber of navigations since store initialization

Reading Navigation State

public class BreadcrumbComponent : StoreComponent
{
private NavigationState Navigation => GetState<NavigationState>();

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// Display current route
builder.AddContent(0, $"Current: {Navigation.CurrentUri}");

// Show back link if there's history
if (Navigation.PreviousUri is not null)
{
builder.AddContent(1, $" | Previous: {Navigation.PreviousUri}");
}
}
}

Security Considerations

The navigation effect validates that URIs are same-origin before navigating. External URLs are not supported by the navigation actions-use standard HTML anchors with target="_blank" for external links:

<!-- For external links, use standard HTML -->
<a href="https://external-site.com/docs" target="_blank" rel="noopener noreferrer">
External Documentation
</a>

<!-- For internal navigation, use actions -->
<button @onclick="@(() => Dispatch(new NavigateAction("/internal-page")))">
Go to Internal Page
</button>

Testing Navigation

Use the StoreTestHarness for unit testing navigation reducers:

[Fact]
public void LocationChangedAction_UpdatesCurrentUri()
{
// Arrange
var harness = StoreTestHarnessFactory.ForFeature<NavigationState>()
.WithReducer<LocationChangedAction>(NavigationReducers.OnLocationChanged);

// Act & Assert
harness.CreateScenario()
.Given(new NavigationState())
.When(new LocationChangedAction("https://example.com/products", false))
.ThenState(state =>
{
state.CurrentUri.Should().Be("https://example.com/products");
state.NavigationCount.Should().Be(1);
});
}

Summary

  • built-in navigation keeps route changes inside the Reservoir action and reducer model
  • ReservoirNavigationProvider bridges browser navigation into store updates through LocationChangedAction
  • navigation actions are limited to same-origin application routes, while external URLs should use normal links

Next Steps

  • Reservoir Overview - Return to the full dispatch model.
  • Actions - Review the action surfaces used by navigation.
  • Effects - Review the effect model that drives navigation side effects.
  • Built-in Lifecycle - Pair navigation state with application lifecycle state.