Publishing a Blazor application means generating the necessary files to run it in production, whether on an ASP.NET Core server or as a static site if we use standalone WebAssembly.
So far we have run everything from Visual Studio or with dotnet run. That’s great for development, but production is a different ballgame: Release compilation, optimized files, real configuration, logs, HTTPS, and all those things that are not so fun until they break.
The good news is that Blazor is part of ASP.NET Core, so the basic flow is the same as in any modern .NET application.
Publishing from the terminal
The most direct way is to use dotnet publish from the project folder.
dotnet publish -c ReleaseThis compiles the application, restores dependencies if necessary, and generates a publish folder with the files ready for deployment.
In current projects, the result is usually located in a path similar to this:
bin/Release/net10.0/publishThe framework version changes depending on the project (net8.0, net9.0, net10.0, etc.). Without additional options, the publication is framework-dependent: the server must have the compatible runtime installed.
If we need an output that includes the runtime, we specify a platform identifier and publish as self-contained:
dotnet publish -c Release -r linux-x64 --self-contained trueThis output is larger and specific to the chosen platform.
In a real application, this command is typically executed by a CI/CD pipeline. Understanding its result allows us to reproduce and diagnose the deployment.
Publishing from Visual Studio
Visual Studio also allows publishing from the Publish wizard.
We can choose destinations such as:
- Local folder.
- IIS.
- Azure App Service.
- Azure Static Web Apps.
- Docker container.
The wizard is fine for getting started, especially if we are deploying to Azure. Still, it is worth knowing that it relies on MSBuild to compile and generate a publish output.
Not all Blazor applications deploy the same way
Here comes the part that is often confusing at first. “Blazor” does not always mean the same type of deployment.
If the application runs on the server, we deploy a normal ASP.NET Core application.
The server needs:
- To have the corresponding .NET runtime installed or to receive a self-contained publication.
- To run the application behind Kestrel, IIS, Nginx, Apache, or Azure App Service.
- To support WebSockets and persistent connections if using server interactivity.
In this case, the browser receives HTML, CSS, JavaScript, and events that travel to the server when the component is interactive.
If we distribute the application across multiple servers, SignalR connections usually require session affinity, unless we use a service that handles that scaling, such as Azure SignalR Service.
If it is a standalone WebAssembly application, the final output is static files.
This means we can serve it from:
- Azure Static Web Apps.
- GitHub Pages.
- Netlify, Vercel, or any static hosting.
- An Nginx or Apache server.
The folder we must deploy is wwwroot within the published result. There we find the .wasm, .dll, .js, .css, and other files that the browser will download.
In projects using InteractiveWebAssembly or InteractiveAuto, we typically have a solution with a server part and a client part.
The server project publishes the application and includes the client’s static resources. Therefore, we must run dotnet publish on the server project, not manually copy the client project’s output.
Although InteractiveAuto can run later components in WebAssembly, the application still requires its ASP.NET Core server.
Variables and configuration
Do not publish secrets within the code, in a compromised appsettings.json in Git, or in files served to the browser.
In production, configuration should come from:
- Environment variables.
- Cloud provider secrets.
- Azure App Configuration.
- Key Vault or other secret store.
- Unversioned configuration files, if the environment allows it.
The code should read configuration using IConfiguration or typed options.
builder.Services.Configure<ApiOptions>(
builder.Configuration.GetSection("Api"));And then use those options from the services.
public class ProductoService
{
private readonly ApiOptions _options;
public ProductoService(IOptions<ApiOptions> options)
{
_options = options.Value;
}
}Watch out for the base path
If we deploy the application at the root of the domain, for example:
https://midominio.com/there is usually no big mystery.
If we deploy it to a subpath:
https://midominio.com/app/we must correctly configure the base path. If we don’t, typical errors will appear: CSS that doesn’t load, broken routes, and scripts returning a 404.
In Blazor WebAssembly standalone, we adjust the <base href="/"> in wwwroot/index.html, including the trailing slash. In Blazor Web App, we configure the base path in ASP.NET Core and in the reverse proxy. Additionally, we check that static resources and routes use that same base.
Publishing to IIS
IIS remains a common choice in Windows environments.
To deploy a Blazor Web App or a server-side application we need:
- Install the ASP.NET Core Hosting Bundle on the server.
- Create a site or application in IIS.
- Copy the contents of the
publishfolder. - Configure the App Pool without the classic .NET CLR.
- Review permissions, HTTPS, and environment variables.
IIS acts as a reverse proxy and the application runs with Kestrel underneath. It is the same model as any modern ASP.NET Core.
Do not share the same App Pool between multiple ASP.NET Core applications if you need to isolate them. Assign each application its own App Pool and permissions.
Publishing as static
For Blazor WebAssembly standalone, deployment can be as simple as uploading static files.
The important point is that the server must return index.html for the internal SPA routes. For example, if the user directly enters:
/productos/42the server should not look for a physical file named productos/42. It must return index.html and let Blazor resolve the route.
This is configured with a fallback or rewrite rule. The provider must also serve the MIME types for WebAssembly files and, when applicable, the compressed versions generated by the publication.