@page discovery
Routes don't have to be hand-declared in one tree - scan assemblies for
@page / [Route] components so templates stay colocated with
their pages.
Point Brouter at your assemblies
AppAssembly and AdditionalAssemblies mirror the built-in Router's
parameters: every component annotated with [Route] (which is what
@page compiles to) is discovered and matched alongside any hand-declared
<Broute> children - Razor class libraries and lazily-loaded assemblies
included. Growing the AdditionalAssemblies list (with a re-render) registers
new routes at runtime.
@* Counter.razor - the route lives next to the page *@ @page "/counter/{start:int}" <h1>Count: @Start</h1> @code { [Parameter] public int Start { get; set; } // from {start:int} [Parameter, SupplyParameterFromQuery] public string? Tab { get; set; } // from ?tab= } @* App router *@ <Brouter AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="@_lazyLoaded"> <Broute Path="/" RedirectTo="/home" /> @* hand-declared routes still work *@ </Brouter>
Parameter binding is identical
Discovered and hand-declared routes bind the same way: route segments to same-named
[Parameter] properties, query values to
[Parameter, SupplyParameterFromQuery] properties. Brouter binds the query
values itself, so this works even where the framework's query supplier isn't registered.
Two framework rules to keep in mind: pair [SupplyParameterFromQuery] with
[Parameter] on routed components, and keep its property types within the set
the framework supplier can parse - for anything else (enums, for example) use Brouter's
opt-in [BrouterQuery] instead (see
route parameters).
Shadowing & ambiguity
Registering two routes that match exactly the same URLs throws - mirroring the built-in
router's AmbiguousMatchException - instead of silently picking one. The one
allowed pairing: a hand-declared <Broute> with the exact template of a
discovered @page shadows it, which is how you attach a
Guard or Loader to an existing page without touching it.
@* ReportsPage declares @page "/reports" - this shadow adds a guard to it: *@ <Broute Path="/reports" Guard="@RequireSignIn" Component="@typeof(ReportsPage)" />
Lazy route loading
OnNavigateAsync runs before matching on every navigation (initial deep links
included). Returned assemblies are scanned and registered within the same
navigation, so the URL that triggered the load lands on the freshly-loaded page - the
WebAssembly LazyAssemblyLoader scenario without the
grow-a-list-and-re-render dance.
<Brouter OnNavigateAsync="@LoadRouteAssemblies" Navigating="@LoadingUi">…</Brouter> private async ValueTask<IEnumerable<Assembly>?> LoadRouteAssemblies(BrouterNavigationContext ctx) { if (ctx.To.Path.StartsWith("/reports")) return await Lazy.LoadAssembliesAsync(["Reports.wasm"]); return null; }
Discovery reflects over the given assemblies - like the built-in Router, keep routable components preserved when trimming.