100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Routing with @page

Learn how Blazor's @page directive maps components to URLs, using route parameters and constraints to build navigable pages.

Routing & FormsBeginner8 min readJul 10, 2026
Analogies

Routing with @page

Blazor uses the @page directive at the top of a .razor file to register a component as a routable endpoint. At startup, the Router component scans the assemblies you configure and builds a route table by reading every @page attribute, similar to how ASP.NET Core MVC discovers controller actions via attribute routing. When a user navigates to a matching URL, either via a browser address bar request or an in-app NavLink click, the router instantiates that component and renders it inside the layout.

🏏

Cricket analogy: Just as an umpire's decision review system checks a predefined set of camera angles before ruling on an LBW, Blazor's Router scans configured assemblies at startup and builds a lookup table of every @page attribute before any URL is matched.

Route Templates and Parameters

A route template like @page "/students/{id}" declares a segment that is captured as a component parameter; the corresponding C# property must be marked [Parameter] and named exactly Id (case-insensitive) with a matching type via a route constraint, e.g. {id:int}. You can stack multiple @page directives on a single component to expose it at several URLs, such as both "/students" and "/students/{id:int}", letting one component handle a list view and a detail view.

🏏

Cricket analogy: Like naming a specific fielding position such as gully so everyone knows exactly which player covers it, the {id:int} constraint tells Blazor exactly which C# property, Id, should catch that URL segment.

Parameters can be made optional by appending a question mark to the constraint, as in {id:int?}, which requires the bound property to be a nullable type like int?. Omitting the segment entirely then renders the component with that parameter left null, so your code must branch on whether a value was supplied instead of assuming a valid id is always present.

🏏

Cricket analogy: Like a Duckworth-Lewis calculation that only kicks in when rain interrupts a match, an optional {id:int?} route only supplies a value when the URL segment is actually present, otherwise the property stays null.

Route Constraints

Blazor route constraints restrict which values a segment accepts before the router even instantiates the component: {id:int}, {id:bool}, {id:datetime}, {id:decimal}, {id:double}, {id:float}, {id:guid}, {id:long}, and {id:non-file-name} are the built-in options. If the actual URL segment can't be parsed as that type, the router treats the route as not matching, so a request for /students/abc against {id:int} falls through to another route or to NotFound content instead of throwing at runtime.

🏏

Cricket analogy: Like a run-out review that only proceeds if the throw actually reaches the stumps in a way the technology can measure, Blazor's {id:int} constraint only matches if the segment can actually be parsed as an integer, otherwise the route is skipped.

razor
@page "/students"
@page "/students/{id:int}"

<h3>Student Directory</h3>

@if (Id is null)
{
    <ul>
        @foreach (var s in students)
        {
            <li><a href="/students/@s.Id">@s.Name</a></li>
        }
    </ul>
}
else
{
    <p>Showing details for student #@Id</p>
}

@code {
    [Parameter]
    public int? Id { get; set; }

    private List<Student> students = new();

    protected override void OnInitialized()
    {
        students = StudentRepository.GetAll();
    }
}

The Router component in App.razor only discovers @page routes from assemblies it's told to scan. If your routable components live in a separate class library (a shared Razor Class Library), you must add it to the Router's AdditionalAssemblies parameter, e.g. <Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(SharedComponent).Assembly }">, or those @page routes will never appear in the route table.

Catch-all Route Parameters

A catch-all parameter, written as {*path}, captures the remainder of the URL including slashes into a single string property, which is useful for components like a documentation viewer or file browser that need an arbitrary nested path such as /docs/guides/advanced/routing. Because the captured segments aren't URL-decoded per segment the way individual route parameters are, catch-all values are decoded as a whole and won't cleanly split back into constrained sub-segments.

🏏

Cricket analogy: Like a fielding captain assigning one player to cover the entire deep boundary rather than a single fixed spot, a catch-all {*path} parameter captures an entire remaining URL path, slashes and all, into one string.

Two components declaring overlapping @page templates, such as "/items/{id}" on one component and "/items/{name}" on another, produces an AmbiguousMatchException at runtime because Blazor cannot deterministically decide which one should win. Route matching order is not guaranteed to follow declaration order, so always design distinct, non-overlapping templates rather than relying on which file happens to load first.

  • The @page directive registers a component as a routable endpoint, and the Router builds its full route table from these attributes at startup.
  • Route parameters are captured into [Parameter] properties whose name matches the segment name and whose type matches the route constraint.
  • A component can carry multiple @page directives to serve several related URLs, such as a list view and a detail view.
  • Appending ? to a constraint, like {id:int?}, makes the segment optional and requires a nullable bound property.
  • Built-in constraints (int, bool, datetime, decimal, double, float, guid, long, non-file-name) reject non-matching segments before the component is instantiated.
  • Catch-all parameters ({*path}) capture an entire remaining nested URL, including slashes, into one string property.
  • Overlapping route templates across components cause an AmbiguousMatchException, so keep templates distinct.

Practice what you learned

Was this page helpful?

Topics covered

#BlazorStudyNotes#MicrosoftTechnologies#RoutingWithPage#Routing#Page#Route#Templates#StudyNotes#SkillVeris#ExamPrep