Blazor applications are often developed locally at the root of a domain, where the application is available at a URL such as /. In production, however, an application may be deployed under a subpath such as /portal/, /admin/, or /customer-app/.

That change looks simple, but it can expose several navigation and resource-loading problems.

Links may point to the wrong location, static files may return 404 errors, browser refreshes may fail, and Blazor routing can appear to work on one page but break after navigating deeper into the application.

ASP.NET Core 11 introduces the BasePath component for Blazor Web Apps, which makes handling the application base path easier by generating the appropriate <base href> value automatically.

This article explains why these problems happen, how BasePath works, when UsePathBase is required, and how to troubleshoot a Blazor application deployed under a subpath.

What Is a Blazor Base Path?

The base path is the URL path where the Blazor application is hosted.

For example, suppose an application is normally hosted like this:

https://example.com/

Its base path is:

/

Now imagine the same application is deployed under:

https://example.com/portal/

The application base path is now:

/portal/

This distinction matters because the browser resolves relative URLs based on the document's base URL.

A Blazor application needs to know that /portal/ is its application root. Otherwise, links and resources can accidentally be resolved against /.

For example:

<base href="/" />

tells the browser that the application is rooted at /.

For an application hosted under /portal/, the equivalent configuration is:

<base href="/portal/" />

The trailing slash is important because it establishes /portal/ as the base directory for relative URLs.

Why Navigation Breaks Under a Subpath

Consider a Blazor application with the following route:

@page "/orders"

<h1>Orders</h1>

When the application runs at:

https://example.com/

the expected URL is:

https://example.com/orders

Now deploy the application at:

https://example.com/portal/

The expected URL becomes:

https://example.com/portal/orders

If the application still assumes / is its root, navigation can produce:

https://example.com/orders

instead of:

https://example.com/portal/orders

The same issue can affect JavaScript files, CSS files, Blazor framework resources, API calls, and other relative resources.

This is why fixing only the router is usually not enough. The application and its hosting pipeline need to agree on the same base path.

ASP.NET Core 11 BasePath Component

ASP.NET Core 11 introduces the BasePath component for Blazor Web Apps.

Instead of manually hardcoding a <base> element for every deployment scenario, you can use:

<BasePath />

The component can generate the appropriate <base href> based on the current request's path base. This is particularly useful when the same application can be deployed at different paths.

A simplified head section can look like this:

<head>
    <meta charset="utf-8" />

    <BasePath />

    <link rel="stylesheet" href="app.css" />
    <HeadOutlet />
</head>

When the application is running at the root, the generated base can resolve to:

<base href="/" />

When the request is being handled under /portal, the base can correspond to:

<base href="/portal/" />

This allows relative application resources and navigation links to resolve against the correct location.

Configuring UsePathBase in ASP.NET Core

The HTML base URL is only one part of the solution.

The ASP.NET Core request pipeline also needs to understand the application's path base.

For a fixed deployment path such as /portal, configure UsePathBase in Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

app.UsePathBase("/portal");

app.UseRouting();

app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

The important detail is the order.

UsePathBase should execute before routing so that routing sees the request after the path base has been processed. Microsoft specifically notes that routing should follow UsePathBase in this configuration.

Conceptually, the request pipeline becomes:

Incoming request
       |
       v
UsePathBase("/portal")
       |
       v
Routing
       |
       v
Blazor endpoints
       |
       v
Component rendering

This is preferable to trying to manually add /portal to every route in the application.

Configure the Base Path in the Application

For a fixed deployment path, you can also configure the base explicitly.

For example:

<base href="/portal/" />

This approach is straightforward when the application will always be deployed under the same path.

However, hardcoding the path can become inconvenient when different environments use different paths.

For example:

Development: /
Testing:     /test/
Production:  /portal/

In that situation, dynamically determining the base path is generally easier to maintain.

Avoid Hardcoding the Subpath in Every Link

One common mistake is adding the deployment path manually to every link.

For example:

<a href="/portal/orders">Orders</a>
<a href="/portal/customers">Customers</a>

This may appear to work in production, but it tightly couples the application to /portal.

If the application is later moved to:

/admin/

every hardcoded URL becomes a maintenance problem.

Prefer application-relative navigation:

<a href="orders">Orders</a>
<a href="customers">Customers</a>

The base URL then determines how those relative URLs are resolved. Microsoft recommends avoiding leading / for relative links when the application is hosted under a non-root path.

NavigationManager and Subpath Hosting

The same principle applies to programmatic navigation.

This is problematic for a subpath deployment:

Navigation.NavigateTo("/orders");

The leading / makes the URL root-relative.

The browser interprets it as:

https://example.com/orders

rather than:

https://example.com/portal/orders

Use a relative route instead:

Navigation.NavigateTo("orders");

Or:

Navigation.NavigateTo("./orders");

This allows the configured application base path to participate in URL resolution.

A component might therefore look like this:

@inject NavigationManager Navigation

<button @onclick="OpenOrders">
    View Orders
</button>

@code {
    private void OpenOrders()
    {
        Navigation.NavigateTo("orders");
    }
}

The important lesson is simple: do not turn a deployment-specific path into an application-wide hardcoded prefix.

API Calls Can Have the Same Problem

Navigation is not the only place where this issue appears.

Consider:

await Http.GetFromJsonAsync<Order[]>("/api/orders");

The leading / makes the request root-relative.

If the API is expected to be available relative to the application base path, this can result in a request to:

/api/orders

instead of:

/portal/api/orders

A relative request can be more appropriate:

await Http.GetFromJsonAsync<Order[]>("api/orders");

Whether this is correct depends on how the API is actually hosted. If the API intentionally lives at the domain root, an absolute root-relative path may be exactly what you want.

The important point is to decide deliberately rather than assuming every URL should begin with /.

Blazor Server and SignalR Under a Subpath

Interactive Server rendering introduces another consideration: the Blazor SignalR connection.

The default Blazor hub path is /_blazor. When the application itself is hosted under a path base, the hub mapping must be consistent with that hosting arrangement. Microsoft documents mapping the Blazor hub with the appropriate base path for these scenarios.

For example, a path-aware configuration can use:

app.MapBlazorHub("/portal/_blazor");

However, the exact configuration depends on how the application is mounted and whether the reverse proxy is already handling the path prefix.

This is an important production consideration: avoid blindly adding the same prefix in both the reverse proxy and ASP.NET Core pipeline.

Reverse Proxy Deployment

Subpath problems are especially common when an application sits behind a reverse proxy.

Consider:

Browser
   |
   v
https://example.com/portal/
   |
   v
Reverse Proxy
   |
   v
ASP.NET Core application

The proxy may expose:

/portal/

while forwarding requests internally to an application that normally expects:

/

In this situation, you need to establish where the path prefix is being handled.

There are two broad approaches:

  1. The reverse proxy preserves the path and ASP.NET Core handles it with UsePathBase.

  2. The reverse proxy removes the prefix before forwarding the request, while the application is configured accordingly.

Mixing both approaches can result in duplicated paths such as:

/portal/portal/orders

Therefore, inspect the actual request path reaching ASP.NET Core before changing multiple layers.

Common Mistakes

Mistake 1: Keeping <base href="/">

If the application is hosted under /portal/ but still contains:

<base href="/" />

relative resources and links can resolve from the wrong location.

Use the appropriate base path or the BasePath component.

Mistake 2: Prefixing Every Link Manually

Avoid:

<a href="/portal/products">Products</a>

throughout the application.

This creates unnecessary coupling between application code and infrastructure.

Mistake 3: Using Root-Relative Navigation

Avoid:

Navigation.NavigateTo("/products");

when /products is supposed to be relative to the Blazor application.

Prefer:

Navigation.NavigateTo("products");

Mistake 4: Calling UseRouting Too Early

This ordering can cause problems:

app.UseRouting();
app.UsePathBase("/portal");

Instead, when UsePathBase is responsible for establishing the application path base, put it before routing:

app.UsePathBase("/portal");
app.UseRouting();

This allows routing to operate against the path-aware request.

Mistake 5: Fixing Only the Browser URL

Changing the <base> tag without considering the ASP.NET Core request pipeline may leave server-side routing, static files, or interactive endpoints incorrectly configured.

A reliable deployment treats the base path as an end-to-end concern.

Troubleshooting Checklist

When a Blazor application works at / but fails under /portal/, check the following.

1. Inspect the Base Tag

Open the browser's developer tools and inspect the generated HTML.

Look for:

<base href="/portal/" />

If you still see:

<base href="/" />

the application doesn't know its deployed base path.

2. Check Navigation URLs

Click a navigation link and verify whether the browser goes to:

/portal/orders

rather than:

/orders

3. Check Network Requests

Look for failed requests such as:

404 /_framework/...
404 /css/...
404 /api/...
404 /_blazor

A pattern of incorrect paths is usually more useful than looking at individual failures.

4. Check Middleware Ordering

Verify that:

app.UsePathBase("/portal");

appears before routing.

5. Check the Reverse Proxy

Determine whether the proxy:

  • preserves /portal, or

  • removes /portal before forwarding.

Do not configure both layers as though they independently own the same prefix.

6. Test Browser Refresh

Client-side navigation can work while a direct request fails.

Test both:

/portal/

and:

/portal/orders

Then refresh the /portal/orders page.

This catches server-side routing and fallback configuration problems that simple link testing may miss.

Local Development with a Non-Root Path

A useful production-like test is running the application locally under the same path structure used in deployment.

For example:

http://localhost:5000/portal/

For Blazor WebAssembly scenarios, the development tooling can be configured with a path-base argument matching the application's base path. The path-base argument itself does not include the trailing slash.

For example:

dotnet watch --pathbase=/portal

A launch profile can also pass the argument automatically:

{
  "profiles": {
    "PortalApp": {
      "commandName": "Project",
      "commandLineArgs": "--pathbase=/portal",
      "launchBrowser": true,
      "launchUrl": "portal"
    }
  }
}

Testing this way is valuable because it exposes hardcoded root-relative URLs before deployment.

Production Best Practices

A reliable subpath deployment should follow a few basic rules.

Area

Recommended Approach

Avoid

Base URL

Use BasePath or a correctly configured <base>

Leaving <base href="/">

Navigation

Use relative routes where appropriate

Hardcoding /portal

NavigationManager

Use orders or ./orders

/orders when it should be app-relative

Routing

Configure UsePathBase before routing when required

Processing the path base after routing

API calls

Match URL style to actual API hosting

Assuming every API belongs under /portal

Reverse proxy

Clearly define which layer owns the prefix

Applying the prefix twice

Testing

Test direct URLs and refreshes

Testing only client-side clicks

Static resources

Verify generated resource URLs

Assuming root hosting

Advantages of Using a Proper Base Path Configuration

Cleaner Application Code

Components don't need to know whether the application is deployed at /, /portal/, or another path.

Easier Deployment

The same application can be deployed behind different URL structures without rewriting every link.

Better Reverse Proxy Compatibility

A correctly configured path base provides a clearer boundary between application routing and infrastructure routing.

Fewer Broken Resource URLs

CSS, JavaScript, framework resources, and application links can resolve against the correct application root.

Easier Testing

You can reproduce subpath hosting locally and catch deployment-specific problems before production.

Disadvantages and Trade-Offs

Additional Hosting Configuration

A subpath deployment requires coordination between the Blazor application, ASP.NET Core pipeline, and potentially the reverse proxy.

More Complex Debugging

When several infrastructure layers rewrite URLs, determining where a path was changed can take time.

Legacy Hardcoded Links May Need Changes

Existing applications that use many root-relative URLs may require a cleanup before they can reliably run under a subpath.

API Architecture Still Matters

A base path does not automatically mean that every API endpoint belongs under that same path. API routing must still reflect the actual architecture.

A Production-Oriented Example

A simple path-aware Program.cs configuration could look like this:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

var app = builder.Build();

app.UsePathBase("/portal");

app.UseRouting();

app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

Then the application head can use:

<head>
    <meta charset="utf-8" />

    <BasePath />

    <link rel="stylesheet" href="app.css" />
    <HeadOutlet />
</head>

And components should use application-relative navigation:

@inject NavigationManager Navigation

<button @onclick="GoToOrders">
    Orders
</button>

@code {
    private void GoToOrders()
    {
        Navigation.NavigateTo("orders");
    }
}

The important part isn't any single line. The configuration works because the browser's base URL, ASP.NET Core's path handling, routing, and application navigation all agree about where the application lives.

Final Takeaway

Running a Blazor application under a subpath is not simply a matter of changing one URL.

The application needs a consistent understanding of its base path across the browser, ASP.NET Core middleware, routing, static resources, interactive endpoints, and any reverse proxy in front of it.

ASP.NET Core 11's BasePath component makes the browser-side part of this configuration easier by allowing Blazor Web Apps to generate the appropriate <base href> based on the request path.

For application code, avoid unnecessarily hardcoding deployment prefixes. Prefer relative navigation where appropriate, configure UsePathBase before routing when ASP.NET Core owns the path prefix, and verify how your reverse proxy handles the URL.

The most useful production test is simple: deploy or run the application under the intended subpath, navigate between pages, open a deep URL directly, refresh it, and inspect the browser's network requests.

If all of those work, your Blazor application is much more likely to behave consistently whether it is hosted at / or behind a path such as /portal/.