Introduced in #64670.
Background and Motivation
When building Blazor applications with nested folder structures (like file explorers or documentation sites), developers face a significant limitation when navigating between sibling pages. For example, with a structure like:
/docs/
/docs/getting-started/
/docs/getting-started/installation.html
/docs/getting-started/configuration.html
When at /docs/getting-started/installation.html and trying to navigate to a sibling page using:
NavigationManager.NavigateTo("configuration.html");
The navigation redirects to /configuration.html (app root) instead of the expected /docs/getting-started/configuration.html which is not expected by some users.
This happens because Blazor resolves relative URLs against the <base> tag (or base URI), not the current document path. This behavior differs from how browsers naturally resolve relative paths and causes friction for developers building applications with hierarchical URL structures.
The same limitation affects NavLink components. This feature allows developers to navigate to URIs relative to the current page path rather than the application's base URI.
Fixes #23615
Proposed API
namespace Microsoft.AspNetCore.Components;
public partial struct NavigationOptions
{
+ public bool RelativeToCurrentUri { get; init; }
}
namespace Microsoft.AspNetCore.Components.Routing;
public class NavLink : ComponentBase
{
+ [Parameter] public bool RelativeToCurrentUri { get; set; }
}
Usage Examples
NavigationManager
@inject NavigationManager NavManager
// Navigate to a sibling page relative to the current URI
<button @onclick='() => NavManager.NavigateTo("details.html", new NavigationOptions { RelativeToCurrentUri = true })'>
View Details
</button>
// Navigate up one directory level
<button @onclick='() => NavManager.NavigateTo("../general.html", new NavigationOptions { RelativeToCurrentUri = true })'>
General Settings
</button>
NavLink
<NavLink href="details" RelativeToCurrentUri="true">View Details</NavLink>
<NavLink href="../orders" RelativeToCurrentUri="true">Orders</NavLink>
Resolution Example
Given:
- Current URL:
https://example.com/app/admin/settings/password/change.html
- Base tag:
<base href="/app/admin/">
| Link |
RelativeToCurrentUri = true |
RelativeToCurrentUri = false (default) |
../general.html |
/app/admin/settings/general.html |
/app/general.html |
./edit |
/app/admin/settings/password/edit |
/app/admin/edit |
../../orders |
/app/admin/settings/orders |
/orders |
Alternative Designs
-
Automatic relative path detection using ./ and ../ - Using standard relative path prefixes to automatically resolve against current URI instead of base URI. However, these already have established meaning (resolved against BaseUri), and changing this would be a breaking change.
-
Separate navigation methods - Creating distinct methods like NavigateToRelative(). This was rejected to avoid API surface bloat when a simple option parameter suffices.
The chosen design is inspired by Angular's router which offers a similar relativeTo option, making Blazor consistent with other major SPA frameworks.
Risks
-
Learning curve - Developers need to understand the distinction between base-relative and document-relative resolution. Clear documentation will be essential.
-
No breaking changes - The default behavior (RelativeToCurrentUri = false) preserves existing functionality, so existing applications are unaffected.
-
Performance - The implementation uses zero-allocation span-based operations and avoids creating Uri objects, minimizing performance impact.
Introduced in #64670.
Background and Motivation
When building Blazor applications with nested folder structures (like file explorers or documentation sites), developers face a significant limitation when navigating between sibling pages. For example, with a structure like:
When at
/docs/getting-started/installation.htmland trying to navigate to a sibling page using:The navigation redirects to
/configuration.html(app root) instead of the expected/docs/getting-started/configuration.htmlwhich is not expected by some users.This happens because Blazor resolves relative URLs against the
<base>tag (or base URI), not the current document path. This behavior differs from how browsers naturally resolve relative paths and causes friction for developers building applications with hierarchical URL structures.The same limitation affects
NavLinkcomponents. This feature allows developers to navigate to URIs relative to the current page path rather than the application's base URI.Fixes #23615
Proposed API
namespace Microsoft.AspNetCore.Components; public partial struct NavigationOptions { + public bool RelativeToCurrentUri { get; init; } }namespace Microsoft.AspNetCore.Components.Routing; public class NavLink : ComponentBase { + [Parameter] public bool RelativeToCurrentUri { get; set; } }Usage Examples
NavigationManager
NavLink
Resolution Example
Given:
https://example.com/app/admin/settings/password/change.html<base href="/app/admin/">RelativeToCurrentUri = trueRelativeToCurrentUri = false(default)../general.html/app/admin/settings/general.html/app/general.html./edit/app/admin/settings/password/edit/app/admin/edit../../orders/app/admin/settings/orders/ordersAlternative Designs
Automatic relative path detection using
./and../- Using standard relative path prefixes to automatically resolve against current URI instead of base URI. However, these already have established meaning (resolved againstBaseUri), and changing this would be a breaking change.Separate navigation methods - Creating distinct methods like
NavigateToRelative(). This was rejected to avoid API surface bloat when a simple option parameter suffices.The chosen design is inspired by Angular's router which offers a similar
relativeTooption, making Blazor consistent with other major SPA frameworks.Risks
Learning curve - Developers need to understand the distinction between base-relative and document-relative resolution. Clear documentation will be essential.
No breaking changes - The default behavior (
RelativeToCurrentUri = false) preserves existing functionality, so existing applications are unaffected.Performance - The implementation uses zero-allocation span-based operations and avoids creating
Uriobjects, minimizing performance impact.