Navigation & history
Everything on IBrouter for moving around imperatively - and what
<BrouterLink> adds over a plain anchor.
Navigate, and know how it went
Navigate(url) is fire-and-forget with optional replace (no new
history entry), forceLoad (full page load), and historyState.
NavigateAsync(url) additionally awaits the whole pipeline and reports
a BrouterNavigationOutcome - so calling code can react when a guard cancelled
or redirected, something Blazor's NavigationManager can't tell you.
| Outcome status | Meaning |
|---|---|
| Succeeded | Destination rendered. |
| Cancelled | A guard/lock called Cancel() - URL unchanged. |
| Redirected | A guard redirected; RedirectedTo has the final URL. |
| NotFound | Nothing matched (after NotFound handling ran). |
| Failed | The pipeline threw; Exception carries it. |
| Superseded | A newer navigation started before this one finished. |
History & entry state
Back() / Forward() move through history;
BackAsync(delta) / ForwardAsync(delta) are awaitable and can jump
several entries at once. Each entry can carry state: pass
historyState: when navigating (or HistoryState on a link) and
read it back from brouter.Location.HistoryState - it survives Back/Forward
per entry, unlike anything keyed to the URL. Serialize to JSON for structured payloads.
The router also classifies every navigation as Push, Replace, or
Pop (Back/Forward) - exposed as NavigationType on guard/hook
contexts and used to pick view-transition direction.
brouter.Navigate("/history-state", historyState: "draft-42"); <BrouterLink Href="/history-state" HistoryState="from-a-link">…</BrouterLink> // later, e.g. after the user presses Back onto that entry: var state = brouter.Location.HistoryState;
Functional query updates
Updating one query parameter shouldn't mean rebuilding the whole URL.
NavigateWithQuery hands you a BrouterQueryBuilder over the
current query - mutate what you need, everything else (other params, the fragment) is
preserved, and the history entry is replaced by default so paging doesn't pollute
Back. The builder API: Set (null removes), SetAll (multi-value,
?tag=a&tag=b), Remove, Clear, plus
Get/Contains for reading.
brouter.NavigateWithQuery(q => q.Set("page", page + 1)); brouter.NavigateWithQuery(q => q.SetAll("tag", tags).Remove("page"));
Named routes & relative URLs
A Named route can be targeted without spelling its URL:
NavigateToName(name, parameters) and ResolveUrl(name, parameters)
fill the template (parameters that aren't in the template are appended as query pairs;
enumerable values emit repeated pairs). Pair them with the generated
BrouterRoutes.Names constants - see the
generator page. URLs are also
route-relative everywhere Brouter accepts one (links, Navigate,
guard redirects): ./sibling, ../up, . and
.. resolve against the current path by segment math, clamped at the root.
brouter.NavigateToName(BrouterRoutes.Names.Counter,
new Dictionary<string, object?> { ["init"] = 7 });
<BrouterLink Href="../sibling">Up one, over one</BrouterLink>BrouterLink
<BrouterLink> renders a real anchor - Ctrl/middle/Shift clicks keep
native browser behavior; only plain left clicks are intercepted.
| Parameter | Behavior |
|---|---|
| Href | Destination; route-relative forms re-resolve after every navigation. |
| Match | Active detection: Prefix (default, segment-boundary aware) or All (exact). Root / always matches exactly. |
| ActiveClass | Class added when active (default "active"); also sets aria-current="page". |
| Replace | Replaces the current history entry instead of pushing. |
| Preload | Intent / Viewport / Render - see Data loading. |
| HistoryState | State attached to the entry created by the click. |
Navigating to the URL you're already on
It does far less than it looks like it does. Blazor reuses a component whose route and
parameters didn't change - the built-in Router behaves the same way - so
Navigate(currentUrl) re-runs the pipeline but leaves the very instance you were
trying to refresh exactly where it was. Three deliberate operations cover what people
actually mean by it, none of them a navigation:
| Call | What it rebuilds |
|---|---|
RevalidateAsync() | Loader data only. Components and their state stay, content stays on screen while it refreshes. See Data loading. |
ClearKeepAlive(includeActive: true) | The component instances, and nothing else: no pipeline, no guards, no loaders - the rebuilt chain keeps the data it already had. See Lifecycle & keep-alive. |
ReloadAsync() | Both: the chain is disposed and matched again, so enter guards, RedirectTo and loaders all re-run (ctx.IsReload). No leave guard can veto it - nothing is being left. |
None of the three moves the URL, the history entry or the scroll position by itself, and none
fires OnNavigating/OnNavigated - the one exception is a guard that
redirects during a reload, which commits a real navigation like any other redirect. The
pipeline page has the
step-by-step comparison.
Not-found handling
When nothing matches, the router follows NotFoundUrl (this demo's
/404 page) or renders an inline <NotFound> fragment without
moving the URL; the OnNotFound callback fires either way. Brouter also
integrates .NET 10's NavigationManager.NotFound(): a component that
discovers mid-render that its entity doesn't exist can hand control to the router's
not-found pipeline while the URL stays put - try it on the outcomes page.