Blazor Routing and Navigation
|
This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14. This section’s bibliography lists the reference material consulted while preparing these pages. |
Blazor routes URLs to components with the same @page mechanism regardless of render mode, and layers
client-side navigation on top of it. See
Blazor Overview and Render Modes for how a routed
component’s render mode is chosen.
@page and route templates
@page "/products/{Id:int}"
@page "/products/{Id:int}/{Slug?}"
@code {
[Parameter] public int Id { get; set; }
[Parameter] public string? Slug { get; set; }
}
Constraints (:int, :guid, :bool, …), optional segments (\{Slug?}), and catch-alls
(\{*rest}) work the same as MVC route templates — see Routing for the
full constraint list. A component can declare more than one @page directive to be reachable at multiple
URLs.
Router, AppAssembly, and AdditionalAssemblies
Routes.razor hosts the Router component, which scans assemblies for @page-annotated components at
startup:
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Shared.Nav).Assembly }">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
AppAssembly is scanned by default; AdditionalAssemblies adds routable components shipped in a referenced
Razor class library (see
Razor class libraries).
NavigationManager
@inject NavigationManager Nav
@implements IDisposable
<button @onclick='() => Nav.NavigateTo("/products")'>Back</button>
<button @onclick='() => Nav.NavigateTo(Nav.GetUriWithQueryParameter("page", 2))'>Next page</button>
@code {
protected override void OnInitialized() => Nav.LocationChanged += OnLocationChanged;
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
=> Console.WriteLine($"Navigated to {e.Location}");
public void Dispose() => Nav.LocationChanged -= OnLocationChanged;
}
NavigateTo(uri, forceLoad: true) bypasses client-side routing for a full page reload;
NavigateTo(uri, replace: true) replaces the current history entry instead of pushing a new one.
GetUriWithQueryParameter builds a new URL with one query parameter added/changed/removed, which is the
supported way to update the query string without hand-building strings.
[SupplyParameterFromQuery]
Bind a component parameter straight from a query-string value instead of reading NavigationManager.Uri
manually:
@page "/products"
@code {
[SupplyParameterFromQuery] public string? Search { get; set; }
[SupplyParameterFromQuery(Name = "p")] public int Page { get; set; } = 1;
}
NavLink
<NavLink href="/products" Match="NavLinkMatch.All">All products</NavLink>
<NavLink href="/products" Match="NavLinkMatch.Prefix">Products section</NavLink>
NavLink renders an <a> and adds the active CSS class when the current URL matches — Match.All requires
an exact match, Match.Prefix (the default) matches any URL that starts with href.
Navigation locks
NavigationLock intercepts an in-app navigation attempt (and, in supporting browsers, a tab close) to confirm
unsaved changes before leaving:
<NavigationLock OnBeforeInternalNavigation="ConfirmLeave" ConfirmExternalNavigation="true" />
@code {
private void ConfirmLeave(LocationChangingContext ctx)
{
if (hasUnsavedChanges) ctx.PreventNavigation();
}
}
NotFoundPage and NavigationManager.NotFound()
dedicated not-found page instead of the component’s own markup:
@page "/products/{Id:int}"
@inject NavigationManager Nav
@code {
[Parameter] public int Id { get; set; }
private ProductDto? product;
protected override async Task OnParametersSetAsync()
{
product = await Products.FindAsync(Id);
if (product is null) Nav.NotFound();
}
}
@* Routes.razor *@
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
...
</Router>
This replaces routing to a hand-rolled "not found" component via <NotFound> for the common case of "the route
matched, but the requested entity does not exist." See
ASP.NET Core Blazor routing and
navigation.
Enhanced navigation and form handling
As covered in the overview page, enhanced
navigation intercepts same-origin link clicks and form posts with JavaScript and patches the DOM instead of a
full reload — it applies across render modes, including Static SSR, and is what makes
SSR form posts feel like an SPA without any
client-side interactivity. Disable it per link/form with data-enhance-nav="false" when a specific navigation
genuinely needs a full page load.
Layouts and nested layouts
@* MainLayout.razor *@
@inherits LayoutComponentBase
<div class="page">
<nav>...</nav>
<main>@Body</main>
</div>
@page "/admin/users"
@layout AdminLayout @* nests inside whatever layout AdminLayout itself declares *@
A layout is a component implementing LayoutComponentBase, rendering @Body where the routed component goes;
@layout on a page (or DefaultLayout on RouteView) selects it, and layouts can nest by one layout’s @Body
containing another RouteView-rendered layout. See
ASP.NET Core Blazor layouts.