Navigation is one of the most common tasks in a Blazor application. A user clicks a menu item, opens a details page, submits a form, or moves between steps in a workflow. Blazor provides two common ways to handle this: NavLink for links in the UI and NavigationManager.NavigateTo for programmatic navigation.
The basic usage is straightforward, but navigation becomes more confusing when an application has nested routes or is hosted under a subpath.
For example, an application might contain routes such as:
/products
/products/details
/products/reviews
A developer may expect details to mean "go to the details page related to the current page." However, how a URI is resolved depends on whether it is root-relative, application-relative, or explicitly relative to the current URI.
Blazor in .NET 11 adds RelativeToCurrentUri support to NavigationManager.NavigateTo and NavLink. This makes it possible to explicitly control whether a relative navigation target is resolved against the current URI.
Understanding this distinction helps prevent broken links, unexpected redirects, and routing problems, especially in applications with nested URLs.
Understanding Relative URLs in Blazor
Before using NavigateTo or NavLink, it is important to understand how the URL is written.
Consider an application running at:
https://example.com/
There are several ways to represent a destination.
Root-Relative URL
A URL beginning with / is root-relative:
/orders
It refers to the domain root.
For example:
https://example.com/orders
Application-Relative URL
A URL without a leading / is relative to the application's base URI in normal Blazor navigation:
orders
If the application is hosted at the root, it can resolve to:
https://example.com/orders
If the application is hosted under a base path such as /portal/, the same application-relative route can resolve within that base path.
Current-URI-Relative Navigation
.NET 11 provides an explicit option for resolving navigation relative to the current URI:
new NavigationOptions
{
RelativeToCurrentUri = true
}
This is useful when an application has hierarchical routes and the target should be interpreted relative to the current location.
These three concepts should not be treated as interchangeable.
Using NavigationManager.NavigateTo
NavigationManager is used when navigation needs to happen from application code.
Inject it into a component:
@inject NavigationManager Navigation
Then call:
Navigation.NavigateTo("orders");
A complete example is:
@page "/dashboard"
@inject NavigationManager Navigation
<h2>Dashboard</h2>
<button @onclick="OpenOrders">
View Orders
</button>
@code {
private void OpenOrders()
{
Navigation.NavigateTo("orders");
}
}
This approach is useful when navigation depends on an action.
For example:
Save data
|
v
Check result
|
v
Navigate to confirmation
The navigation is part of the application workflow rather than simply being a link displayed to the user.
When to Use NavLink
NavLink is designed for navigation elements such as menus, sidebars, and navigation bars.
For example:
<nav>
<NavLink href="dashboard">
Dashboard
</NavLink>
<NavLink href="orders">
Orders
</NavLink>
<NavLink href="customers">
Customers
</NavLink>
</nav>
One important advantage of NavLink is that it understands the current navigation location and can apply an active CSS class to the matching link.
A typical navigation menu might therefore look like this:
<nav class="sidebar">
<NavLink href="dashboard">
Dashboard
</NavLink>
<NavLink href="orders">
Orders
</NavLink>
<NavLink href="customers">
Customers
</NavLink>
</nav>
This is generally preferable to creating a button and calling NavigateTo when the only purpose of the element is to take the user to another page.
NavigateTo vs NavLink
Although both APIs can navigate between pages, they serve different purposes.
Feature |
|
|
|---|---|---|
Primary purpose | Programmatic navigation | Navigation UI |
Typical usage | Button, form, workflow | Menu, sidebar, navigation bar |
Active CSS state | No | Yes |
Event-driven navigation | Yes | Not normally required |
| Supported | Supported |
Replace browser history entry | Supported | Not directly configured in the same way |
Force full page load | Supported | Not its primary purpose |
A useful rule is:
User selects a destination → NavLink
Application decides a destination → NavigateTo
This isn't an absolute rule, but it makes component design easier to understand.
The Problem with Leading Slashes
One of the most common navigation mistakes is adding / without considering what it means.
For example:
Navigation.NavigateTo("/orders");
The destination is root-relative.
If the application is hosted at the domain root, this may be exactly what you need.
However, suppose the application is deployed under:
/portal/
The developer may actually want:
/portal/orders
but a root-relative destination can point to:
/orders
This distinction becomes especially important for applications deployed behind reverse proxies or under a virtual directory.
For application-relative navigation, use:
Navigation.NavigateTo("orders");
instead of automatically using:
Navigation.NavigateTo("/orders");
The same principle applies to NavLink.
Prefer:
<NavLink href="orders">
Orders
</NavLink>
when the link should be relative to the application's base path.
Nested Routes and Current URI Navigation
Now consider a documentation application with routes like:
/docs
/docs/getting-started
/docs/getting-started/install
/docs/getting-started/configuration
Suppose the user is currently on:
/docs/getting-started/install
and the application needs to navigate to another page relative to that location.
A normal application-relative navigation and current-URI-relative navigation have different meanings.
.NET 11 provides RelativeToCurrentUri for this purpose.
For example:
Navigation.NavigateTo(
"configuration",
new NavigationOptions
{
RelativeToCurrentUri = true
});
The important part is:
RelativeToCurrentUri = true
It tells the navigation system that the supplied URI should be resolved relative to the current URI rather than simply treating it as a normal application-base-relative destination.
This is particularly useful when reusable components operate inside nested route structures.
Using RelativeToCurrentUri with NavLink
The same concept is available with NavLink.
For example:
<NavLink href="configuration"
RelativeToCurrentUri="true">
Configuration
</NavLink>
This is useful for navigation components that are designed to work relative to the page where they are rendered.
For example, imagine a reusable component:
<div class="section-navigation">
<NavLink href="overview"
RelativeToCurrentUri="true">
Overview
</NavLink>
<NavLink href="configuration"
RelativeToCurrentUri="true">
Configuration
</NavLink>
<NavLink href="history"
RelativeToCurrentUri="true">
History
</NavLink>
</div>
The component does not have to hardcode a complete route hierarchy.
Its navigation can be interpreted in relation to the current location.
Choosing Between Normal Relative Navigation and Current URI Navigation
The most important question is:
What should the destination be relative to?
Consider this example:
Application base:
/admin/
Current URI:
/admin/users/
Desired destination:
/admin/reports/
If the destination is a normal application route, use application-relative navigation:
Navigation.NavigateTo("reports");
If the navigation component is intentionally designed around the current URI hierarchy, current-URI-relative navigation can be used:
Navigation.NavigateTo(
"reports",
new NavigationOptions
{
RelativeToCurrentUri = true
});
The correct choice depends on the route structure.
Do not use RelativeToCurrentUri simply because it is available. Use it when the navigation relationship is actually based on the current URI.
Using NavigationOptions
.NET provides NavigationOptions for controlling navigation behavior.
For example:
var options = new NavigationOptions
{
RelativeToCurrentUri = true,
ReplaceHistoryEntry = false,
ForceLoad = false
};
Navigation.NavigateTo("configuration", options);
The options make the navigation intent explicit.
RelativeToCurrentUri
Controls whether the target is resolved relative to the current URI.
RelativeToCurrentUri = true
ReplaceHistoryEntry
Controls whether the current browser history entry is replaced.
ReplaceHistoryEntry = true
This can be useful for redirects where the user shouldn't return to the intermediate URL by pressing the browser Back button.
ForceLoad
Controls whether Blazor should bypass client-side routing and perform a full browser load.
ForceLoad = true
This should not be used as a general fix for routing problems.
If a URL is incorrect, ForceLoad does not make the URL correct.
Relative Navigation with Query Parameters
Relative navigation can also contain query parameters.
For example:
Navigation.NavigateTo("orders?status=pending");
This can be useful when filters or search criteria are represented in the URL.
A component might use:
<button @onclick="ShowPendingOrders">
Pending Orders
</button>
@code {
private void ShowPendingOrders()
{
Navigation.NavigateTo("orders?status=pending");
}
}
Keep query-string construction readable. If an application has many parameters, consider using Blazor's supported query-string binding features rather than manually concatenating large URLs throughout components.
Relative Navigation with Fragments
A URI can also contain a fragment:
Navigation.NavigateTo("orders#history");
Fragments are useful when a page contains different sections and the browser should move to a particular location.
For example:
<h2 id="history">Order History</h2>
Then:
Navigation.NavigateTo("orders#history");
can target the history section of the orders page.
Again, whether the URI should be application-relative or current-URI-relative depends on the route structure.
NavigationManager Uri and BaseUri
When troubleshooting navigation, NavigationManager provides two particularly useful properties.
Navigation.Uri
returns the current absolute URI.
Navigation.BaseUri
returns the application's base URI.
For example:
@inject NavigationManager Navigation
<p>Current URI: @Navigation.Uri</p>
<p>Base URI: @Navigation.BaseUri</p>
This is useful when an application behaves differently between development and production.
For example, you might discover that development uses:
https://localhost:5001/
while production uses:
https://example.com/portal/
That difference can explain why a root-relative link works locally but fails after deployment.
Working with Base-Relative Paths
NavigationManager also provides ToBaseRelativePath.
For example:
var relativePath =
Navigation.ToBaseRelativePath(Navigation.Uri);
This is useful when an application needs to inspect the current location without manually removing the base URI.
For example:
private string GetCurrentRoute()
{
return Navigation.ToBaseRelativePath(Navigation.Uri);
}
This approach is preferable to manually manipulating strings when working with application base paths.
A Practical Navigation Component
A real application might have a sidebar like this:
@inject NavigationManager Navigation
<nav class="sidebar">
<NavLink href="dashboard">
Dashboard
</NavLink>
<NavLink href="orders">
Orders
</NavLink>
<NavLink href="customers">
Customers
</NavLink>
</nav>
@code {
private void OpenSettings()
{
Navigation.NavigateTo("settings");
}
}
The menu uses NavLink because these are normal destinations.
If a settings page is opened only after a particular application action, NavigateTo is more appropriate:
private async Task CompleteSetup()
{
await SaveSettingsAsync();
Navigation.NavigateTo("settings");
}
This keeps the navigation responsibility aligned with the purpose of each API.
Common Mistakes
Using Root-Relative URLs Everywhere
This:
<NavLink href="/orders">
Orders
</NavLink>
is not automatically wrong.
The problem occurs when the application is expected to treat orders as a route within its application base.
Use:
<NavLink href="orders">
Orders
</NavLink>
when application-relative navigation is intended.
Using NavigateTo for Every Link
This pattern:
<button @onclick="OpenOrders">
Orders
</button>
with:
private void OpenOrders()
{
Navigation.NavigateTo("orders");
}
works, but it is unnecessary when the element is simply a navigation link.
A NavLink communicates the intent more clearly:
<NavLink href="orders">
Orders
</NavLink>
Assuming RelativeToCurrentUri Is Always Better
RelativeToCurrentUri solves a specific routing problem.
It should not replace ordinary application-relative navigation everywhere.
First identify the relationship:
Domain root
Application base
Current URI
Then select the appropriate navigation style.
Hardcoding Deployment Paths
Avoid:
Navigation.NavigateTo("/portal/orders");
if /portal is simply the deployment location.
Application components should generally not need to know infrastructure-specific prefixes.
Using ForceLoad as a Routing Fix
Avoid this approach:
Navigation.NavigateTo("/orders", forceLoad: true);
simply because normal navigation doesn't work.
First verify:
The route exists.
The URI is correct.
The application base is correct.
The hosting configuration is correct.
The server can handle the destination.
Troubleshooting Navigation Problems
When navigation behaves unexpectedly, follow a consistent process.
Step 1: Inspect the Current URI
Display:
Navigation.Uri
and determine exactly where the application currently is.
Step 2: Inspect the Base URI
Check:
Navigation.BaseUri
This is especially important when the application is hosted under a subpath.
Step 3: Look for Leading Slashes
Search the component for:
href="/
and:
NavigateTo("/
These are often the first places to investigate.
Step 4: Verify the Route
Make sure the destination actually exists.
For example:
@page "/orders"
does not automatically create:
/orders/details
unless another component defines that route.
Step 5: Test Direct Navigation
Don't test only by clicking a menu item.
Copy the resulting URL into the browser and refresh the page.
This helps distinguish a client-side navigation problem from a server-side hosting problem.
Step 6: Test Under the Production Base Path
If production uses:
/app/
test the application under the same structure during development or staging.
Many navigation issues remain hidden when the application is always tested at /.
Best Practices
Use
NavLinkfor normal application navigation menus.Use
NavigateTowhen navigation is triggered by application logic.Understand the difference between root-relative and application-relative URLs.
Avoid adding a leading
/unless root-relative navigation is intentional.Use
RelativeToCurrentUriwhen the destination should be resolved against the current URI.Don't hardcode reverse-proxy or deployment prefixes into components.
Use
NavigationManager.UriandBaseUriwhen troubleshooting.Don't use
ForceLoadto hide an underlying routing problem.Test nested routes and direct browser refreshes.
Keep navigation code simple when standard relative navigation is sufficient.
Advantages
Cleaner Navigation Code
Developers can express navigation intent without manually constructing complete URLs.
Better Support for Nested Applications
Current-URI-relative navigation is useful for components that operate inside hierarchical route structures.
Better Reusable Components
A reusable navigation component can work relative to its rendering context instead of depending on one hardcoded route hierarchy.
Improved Subpath Compatibility
Application-relative navigation reduces unnecessary dependencies on the deployment location.
Clear Separation of Responsibilities
NavLink handles navigation UI, while NavigateTo handles programmatic navigation.
Disadvantages and Trade-Offs
Relative URI Semantics Can Be Confusing
Developers unfamiliar with URL resolution may not immediately understand the difference between the application base and current URI.
Nested Routes Require Care
A relative destination can produce an unexpected result when the intended reference point is not clearly defined.
Migration May Require Code Changes
Applications with many hardcoded root-relative URLs may need to review existing navigation code before moving to a subpath deployment.
Infrastructure Still Matters
Correct Blazor navigation cannot compensate for incorrect reverse-proxy, routing, or base-path configuration.
Production Checklist
Before deploying a Blazor application, verify the following:
Check | What to Verify |
|---|---|
Navigation menus | Use |
Programmatic navigation | Use |
Leading | Confirm root-relative navigation is intentional |
Nested routes | Verify the intended reference URI |
| Use only when current-URI resolution is required |
Base path | Confirm the application base is correct |
Reverse proxy | Confirm path prefixes aren't duplicated |
Deep links | Open nested URLs directly |
Refresh | Refresh deep URLs in the browser |
Query parameters | Verify filters and state survive navigation |
History | Check Back-button behavior after redirects |
ForceLoad | Use only when a full browser load is actually required |
Final Takeaway
Blazor navigation is easier to maintain when every URL has a clearly defined reference point.
Use NavLink when you are building normal navigation UI and NavigationManager.NavigateTo when navigation is part of application logic.
For ordinary application-relative navigation, a simple route such as:
Navigation.NavigateTo("orders");
is often enough.
When a destination needs to be interpreted relative to the current URI, .NET 11's RelativeToCurrentUri option provides a more explicit solution:
Navigation.NavigateTo(
"configuration",
new NavigationOptions
{
RelativeToCurrentUri = true
});
The same capability is available with NavLink:
<NavLink href="configuration"
RelativeToCurrentUri="true">
Configuration
</NavLink>
The important thing is not to memorize one syntax and use it everywhere. Instead, determine whether the destination is relative to the domain root, the application's base path, or the current URI.
That small distinction can prevent a large class of navigation bugs, especially in applications with nested routes, reusable components, and non-root deployments.

Join the conversation! Your thoughts help the community grow.