Skip to content

Lifecycle & keep-alive

Know when your routed component arrives, is revisited, or leaves - and optionally keep it alive (state and all) while the user is elsewhere.

The route lifecycle

Implement IBrouterRoute on a routed component - the router discovers it automatically for Component-rendered routes, no registration needed. Arrivals fire root→leaf after the render commits; departures fire leaf→root before it. All callbacks have default no-op implementations, so you override only what you need. None of them run during static prerendering.

CallbackWhenPayload highlights
OnActivatedAsyncRoute became active (first visit or return from keep-alive).IsFirstActivation, Location
OnRenavigatedAsyncSame route re-committed with different parameters or query.Location, PreviousLocation
OnDeactivatedAsyncRoute left. Sync part guaranteed to run before Dispose.Reason (Hidden / Disposing), Location
OnDeactivatingAsyncAbout to be left - awaited, cancellable (navigation lock).To, Cancel(), Redirect(), CancellationToken
OnRenavigatingAsyncAbout to re-commit with new params - awaited, cancellable.same as above

BrouterRouteBase is the convenience base class: it wires the cascaded context for you, adds sync+async virtual pairs (OnActivated / OnActivatedAsync, ...), an IsActive property, and an automatic StateHasChanged after activation. For components that aren't the route root - a widget inside a <Content> fragment, or a descendant at any depth - take the cascaded BrouterRouteContext and call Register(this) / Unregister(this) yourself.

Keep-alive

Normally a route's component is disposed on leave and rebuilt on return. With KeepAlive="true" the rendered content is retained in a hidden container instead - scroll position, form input, timers, all component state survive. Deactivation then reports Reason = Hidden rather than Disposing, which is the cue to pause background work (the sticky-note demo pauses its timer in OnDeactivated and resumes in OnActivated). Compare /sticky with the /fleeting control route: same component, no keep-alive, state resets every visit.

<Broute Path="/sticky" KeepAlive="true"> ... </Broute>

Per-parameter instances & eviction

KeepAliveMax sets the retained-instance budget. At the default (BrouterOptions.DefaultKeepAliveMax, 1) a single instance is re-bound as parameters change. Above 1, each distinct set of template parameter values gets its own instance - /notes/1 and /notes/2 are two live components; query-string variations share one - with least-recently-used eviction beyond the budget. brouter.ClearKeepAlive() disposes everything retained (sign-out, memory pressure) - but never the page on screen, which keeps its instance and its state.

brouter.ClearKeepAlive(includeActive: true) is the version that includes it: the matched chain's live components are deactivated (Disposing), disposed and rebuilt in place, so the visible page comes back as a brand-new instance in its initial state - no restored scroll, filters or form drafts. It is deliberately not a navigation and not a reload: the URL, history entry and scroll position don't move, no hook or guard runs, and loaders don't re-run (the rebuilt chain is handed the data it already loaded). Reach for it after switching tenant or impersonating a user, where re-navigating to the current URL cannot help - Blazor reuses a component whose route and parameters didn't change, so the stale instance simply stays. When the page's data is stale too, ReloadAsync() runs the whole pipeline again.

<Broute Path="/notes/{id:int}" KeepAlive="true" KeepAliveMax="2"
        Component="@typeof(NotebookPage)" />

Singleton re-commit vs disposal

The lifecycle log makes the instance model tangible: /lifecycle/1 → /lifecycle/2 keeps the route matched, so the same instance re-commits and OnRenavigatedAsync fires with old and new locations - no dispose, no rebuild. Leaving the route entirely fires OnDeactivatedAsync with Reason = Disposing. The page stamps each instance with a random id so you can verify it never changes across renavigations.