How to make Blazor Static support trailing slashes in URLs?
Oh, Blazor Static - the dazzling, lightning-fast static site generator that turns your Blazor components into HTML gold. One of the rare pieces of tech that makes me happy day after day that I'm using it for my static sites.
Too bad it's a real diva when it comes to URLs! Stubborn enough to refuse to serve /about/ when /about works just fine.
And sure - it's not really the library's fault. There is no server-side routing to handle the redirection between the two URL styles. But still - it's a pain.
But fear not, dear developer, for salvation lies in a cheeky configuration tweak!
By duping Blazor Static into spitting out both file-based and directory-based URLs, you'll generate the magic files that make both slash flavors dance in harmony. Not only does this slay the routing nightmare, but it also keeps the goshdarn SEO gods appeased.
So buckle up, and let's vibe-code our way to duplicating content like it's 1999!
Background
Table of Contents
Table of Contents
Blazor Static is a static site generator for Blazor applications, which allows you to pre-render your Blazor components into static HTML files. This can significantly improve the performance of your Blazor application, especially for SEO and initial load times.
It is simply beautiful. It is elegant. It is fast. It is everything you want from a static site generator.
But do you know what is not beautiful? URLs that do not work as expected.
And while it's pretty normal to expect a URL to work with or without a trailing slash, Blazor Static does not support this out of the box. Not without a router taking care of it - and you can't have one when it's a true static site.
This means that if you have a URL like /about, it will not work if you try to access it as /about/.
What now?
Note: Blazor Static is different from Blazor WebAssembly, which is a client-side framework that runs in the browser. Blazor Static generates static HTML files that can be served by any web server, without the need for a Blazor runtime. You can serve a Blazor WebAssembly app from a simple file host and make it essentially a static site, but it is not the same as Blazor Static.
Problem
An obvious solution - and one that GitHub Copilot will endlessly apply, even though it only appears to fix the issue - would be to use page directives for your Razor pages, like this:
@page "/about"
@page "/about/"
But the keen ones among you will quickly realize that this does not solve the problem.
In fact, this won't even build. You will get an error like this:

And below in text form:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
/about (/about)
/about/ (/about/)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(Span`1 candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, Span`1 candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, Span`1 candidateState)
at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
Reason
Alright. The picture should be clear by now.
The reason this is a problem is that static site generators like Blazor Static generate static HTML files based on the URLs defined in your Razor components. When you define a URL without a trailing slash, the static site generator creates a file for that URL, but it does not create a separate file for the same URL with a trailing slash.
In theory, having multiple paths - one without a trailing slash and one with a trailing slash - pointing to the same content would solve this. But then we run into the problem of ambiguous routes, as ASP.NET Core routing usually handles both routes, and does not take kindly to us trying to define them separately.
But even if we DID actually fix routing... Would that instruct Blazor Static to generate two separate files for the same content? I don't think it will - the trailing slash should match a directory, not a file.
So we need to find some other way to solve this.
Solution
And if you read this far, you probably already know how to solve this. Or at least have a hunch.
You need to somehow fool Blazor Static into generating both versions of the URL - both a directory (with an "index.html" file inside) and a file (without a trailing slash).
builder.WebHost.UseStaticWebAssets();
builder.Services.AddBlazorStaticService(opt =>
{
// ... some implementation omitted for brevity
var blogFiles = Directory.GetFiles(Path.Combine("Content", "Blog"), "*.md");
foreach (var filePath in blogFiles)
{
var fileName = Path.GetFileNameWithoutExtension(filePath);
// Always generate the filename-based URLs for backward compatibility
opt.PagesToGenerate.Add(new PageToGenerate($"/{fileName}", $"{fileName}.html"));
// Additionally, generate directory-based URLs to support trailing slashes
opt.PagesToGenerate.Add(new PageToGenerate($"/{fileName}", $"{fileName}/index.html"));
}
}
... and that's it! Now you've got both versions of the URL working for your Blazor Static site.
The next step is to make sure your canonical URLs are set correctly, so that search engines know which version to index. You can do this by adding a <link rel="canonical" href="..."> tag in the <head> section of your Razor pages, or use the
Comments
No comments yet.