Host and deploy ASP.NET Core Blazor WebAssembly

By Luke Latham, Rainer Stropek, Daniel Roth, Ben Adams, and Safia Abdalla

With the Blazor WebAssembly hosting model:

  • The Blazor app, its dependencies, and the .NET runtime are downloaded to the browser in parallel.
  • The app is executed directly on the browser UI thread.

The following deployment strategies are supported:

  • The Blazor app is served by an ASP.NET Core app. This strategy is covered in the Hosted deployment with ASP.NET Core section.
  • The Blazor app is placed on a static hosting web server or service, where .NET isn't used to serve the Blazor app. This strategy is covered in the Standalone deployment section, which includes information on hosting a Blazor WebAssembly app as an IIS sub-app.

Brotli precompression

When a Blazor WebAssembly app is published, the output is precompressed using the Brotli compression algorithm at the highest level to reduce the app size and remove the need for runtime compression.

For IIS web.config compression configuration, see the IIS: Brotli and Gzip compression section.

Rewrite URLs for correct routing

Routing requests for page components in a Blazor WebAssembly app isn't as straightforward as routing requests in a Blazor Server, hosted app. Consider a Blazor WebAssembly app with two components:

  • Main.razor – Loads at the root of the app and contains a link to the About component (href="About").
  • About.razorAbout component.

When the app's default document is requested using the browser's address bar (for example, https://www.contoso.com/):

  1. The browser makes a request.
  2. The default page is returned, which is usually index.html.
  3. index.html bootstraps the app.
  4. Blazor's router loads, and the Razor Main component is rendered.

In the Main page, selecting the link to the About component works on the client because the Blazor router stops the browser from making a request on the Internet to www.contoso.com for About and serves the rendered About component itself. All of the requests for internal endpoints within the Blazor WebAssembly app work the same way: Requests don't trigger browser-based requests to server-hosted resources on the Internet. The router handles the requests internally.

If a request is made using the browser's address bar for www.contoso.com/About, the request fails. No such resource exists on the app's Internet host, so a 404 - Not Found response is returned.

Because browsers make requests to Internet-based hosts for client-side pages, web servers and hosting services must rewrite all requests for resources not physically on the server to the index.html page. When index.html is returned, the app's Blazor router takes over and responds with the correct resource.

When deploying to an IIS server, you can use the URL Rewrite Module with the app's published web.config file. For more information, see the IIS section.

Hosted deployment with ASP.NET Core

A hosted deployment serves the Blazor WebAssembly app to browsers from an ASP.NET Core app that runs on a web server.

The client Blazor WebAssembly app is published into the /bin/Release/{TARGET FRAMEWORK}/publish/wwwroot folder of the server app, along with any other static web assets of the server app. The two apps are deployed together. A web server that is capable of hosting an ASP.NET Core app is required. For a hosted deployment, Visual Studio includes the Blazor WebAssembly App project template (blazorwasm template when using the dotnet new command) with the Hosted option selected (-ho|--hosted when using the dotnet new command).

For more information on ASP.NET Core app hosting and deployment, see Host and deploy ASP.NET Core.

For information on deploying to Azure App Service, see Publish an ASP.NET Core app to Azure with Visual Studio.

Standalone deployment

A standalone deployment serves the Blazor WebAssembly app as a set of static files that are requested directly by clients. Any static file server is able to serve the Blazor app.

Standalone deployment assets are published into the /bin/Release/{TARGET FRAMEWORK}/publish/wwwroot folder.

Azure App Service

Blazor WebAssembly apps can be deployed to Azure App Services on Windows, which hosts the app on IIS.

Deploying a standalone Blazor WebAssembly app to Azure App Service for Linux isn't currently supported. A Linux server image to host the app isn't available at this time. Work is in progress to enable this scenario.

IIS

IIS is a capable static file server for Blazor apps. To configure IIS to host Blazor, see Build a Static Website on IIS.

Published assets are created in the /bin/Release/{TARGET FRAMEWORK}/publish folder. Host the contents of the publish folder on the web server or hosting service.

web.config

When a Blazor project is published, a web.config file is created with the following IIS configuration:

  • MIME types are set for the following file extensions:
    • .dllapplication/octet-stream
    • .jsonapplication/json
    • .wasmapplication/wasm
    • .woffapplication/font-woff
    • .woff2application/font-woff
  • HTTP compression is enabled for the following MIME types:
    • application/octet-stream
    • application/wasm
  • URL Rewrite Module rules are established:
    • Serve the sub-directory where the app's static assets reside (wwwroot/{PATH REQUESTED}).
    • Create SPA fallback routing so that requests for non-file assets are redirected to the app's default document in its static assets folder (wwwroot/index.html).

Use a custom web.config

To use a custom web.config file, place the custom web.config file at the root of the project folder and publish the project.

Install the URL Rewrite Module

The URL Rewrite Module is required to rewrite URLs. The module isn't installed by default, and it isn't available for install as a Web Server (IIS) role service feature. The module must be downloaded from the IIS website. Use the Web Platform Installer to install the module:

  1. Locally, navigate to the URL Rewrite Module downloads page. For the English version, select WebPI to download the WebPI installer. For other languages, select the appropriate architecture for the server (x86/x64) to download the installer.
  2. Copy the installer to the server. Run the installer. Select the Install button and accept the license terms. A server restart isn't required after the install completes.

Configure the website

Set the website's Physical path to the app's folder. The folder contains:

  • The web.config file that IIS uses to configure the website, including the required redirect rules and file content types.
  • The app's static asset folder.

Host as an IIS sub-app

If a standalone app is hosted as an IIS sub-app, perform either of the following:

  • Disable the inherited ASP.NET Core Module handler.

    Remove the handler in the Blazor app's published web.config file by adding a <handlers> section to the file:

    <handlers>
      <remove name="aspNetCore" />
    </handlers>
    
  • Disable inheritance of the root (parent) app's <system.webServer> section using a <location> element with inheritInChildApplications set to false:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <location path="." inheritInChildApplications="false">
        <system.webServer>
          <handlers>
            <add name="aspNetCore" ... />
          </handlers>
          <aspNetCore ... />
        </system.webServer>
      </location>
    </configuration>
    

Removing the handler or disabling inheritance is performed in addition to configuring the app's base path. Set the app base path in the app's index.html file to the IIS alias used when configuring the sub-app in IIS.

Brotli and Gzip compression

IIS can be configured via web.config to serve Brotli or Gzip compressed Blazor assets. For an example configuration, see web.config.

Troubleshooting

If a 500 - Internal Server Error is received and IIS Manager throws errors when attempting to access the website's configuration, confirm that the URL Rewrite Module is installed. When the module isn't installed, the web.config file can't be parsed by IIS. This prevents the IIS Manager from loading the website's configuration and the website from serving Blazor's static files.

For more information on troubleshooting deployments to IIS, see Troubleshoot ASP.NET Core on Azure App Service and IIS.

Azure Storage

Azure Storage static file hosting allows serverless Blazor app hosting. Custom domain names, the Azure Content Delivery Network (CDN), and HTTPS are supported.

When the blob service is enabled for static website hosting on a storage account:

  • Set the Index document name to index.html.
  • Set the Error document path to index.html. Razor components and other non-file endpoints don't reside at physical paths in the static content stored by the blob service. When a request for one of these resources is received that the Blazor router should handle, the 404 - Not Found error generated by the blob service routes the request to the Error document path. The index.html blob is returned, and the Blazor router loads and processes the path.

For more information, see Static website hosting in Azure Storage.

Nginx

The following nginx.conf file is simplified to show how to configure Nginx to send the index.html file whenever it can't find a corresponding file on disk.

events { }
http {
    server {
        listen 80;

        location / {
            root /usr/share/nginx/html;
            try_files $uri $uri/ /index.html =404;
        }
    }
}

For more information on production Nginx web server configuration, see Creating NGINX Plus and NGINX Configuration Files.

Nginx in Docker

To host Blazor in Docker using Nginx, setup the Dockerfile to use the Alpine-based Nginx image. Update the Dockerfile to copy the nginx.config file into the container.

Add one line to the Dockerfile, as shown in the following example:

FROM nginx:alpine
COPY ./bin/Release/netstandard2.0/publish /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf

Apache

To deploy a Blazor WebAssembly app to CentOS 7 or later:

  1. Create the Apache configuration file. The following example is a simplified configuration file (blazorapp.config):

    <VirtualHost *:80>
        ServerName www.example.com
        ServerAlias *.example.com
    
        DocumentRoot "/var/www/blazorapp"
        ErrorDocument 404 /index.html
    
        AddType application/wasm .wasm
        AddType application/octet-stream .dll
    
        <Directory "/var/www/blazorapp">
            Options -Indexes
            AllowOverride None
        </Directory>
    
        <IfModule mod_deflate.c>
            AddOutputFilterByType DEFLATE text/css
            AddOutputFilterByType DEFLATE application/javascript
            AddOutputFilterByType DEFLATE text/html
            AddOutputFilterByType DEFLATE application/octet-stream
            AddOutputFilterByType DEFLATE application/wasm
            <IfModule mod_setenvif.c>
            BrowserMatch ^Mozilla/4 gzip-only-text/html
            BrowserMatch ^Mozilla/4.0[678] no-gzip
            BrowserMatch bMSIE !no-gzip !gzip-only-text/html
        </IfModule>
        </IfModule>
    
        ErrorLog /var/log/httpd/blazorapp-error.log
        CustomLog /var/log/httpd/blazorapp-access.log common
    </VirtualHost>
    
  2. Place the Apache configuration file into the /etc/httpd/conf.d/ directory, which is the default Apache configuration directory in CentOS 7.

  3. Place the app's files into the /var/www/blazorapp directory (the location specified to DocumentRoot in the configuration file).

  4. Restart the Apache service.

For more information, see mod_mime and mod_deflate.

GitHub Pages

To handle URL rewrites, add a 404.html file with a script that handles redirecting the request to the index.html page. For an example implementation provided by the community, see Single Page Apps for GitHub Pages (rafrex/spa-github-pages on GitHub). An example using the community approach can be seen at blazor-demo/blazor-demo.github.io on GitHub (live site).

When using a project site instead of an organization site, add or update the <base> tag in index.html. Set the href attribute value to the GitHub repository name with a trailing slash (for example, my-repository/.

Host configuration values

Blazor WebAssembly apps can accept the following host configuration values as command-line arguments at runtime in the development environment.

Content root

The --contentroot argument sets the absolute path to the directory that contains the app's content files (content root). In the following examples, /content-root-path is the app's content root path.

  • Pass the argument when running the app locally at a command prompt. From the app's directory, execute:

    dotnet run --contentroot=/content-root-path
    
  • Add an entry to the app's launchSettings.json file in the IIS Express profile. This setting is used when the app is run with the Visual Studio Debugger and from a command prompt with dotnet run.

    "commandLineArgs": "--contentroot=/content-root-path"
    
  • In Visual Studio, specify the argument in Properties > Debug > Application arguments. Setting the argument in the Visual Studio property page adds the argument to the launchSettings.json file.

    --contentroot=/content-root-path
    

Path base

The --pathbase argument sets the app base path for an app run locally with a non-root relative URL path (the <base> tag href is set to a path other than / for staging and production). In the following examples, /relative-URL-path is the app's path base. For more information, see App base path.

Important

Unlike the path provided to href of the <base> tag, don't include a trailing slash (/) when passing the --pathbase argument value. If the app base path is provided in the <base> tag as <base href="/CoolApp/"> (includes a trailing slash), pass the command-line argument value as --pathbase=/CoolApp (no trailing slash).

  • Pass the argument when running the app locally at a command prompt. From the app's directory, execute:

    dotnet run --pathbase=/relative-URL-path
    
  • Add an entry to the app's launchSettings.json file in the IIS Express profile. This setting is used when running the app with the Visual Studio Debugger and from a command prompt with dotnet run.

    "commandLineArgs": "--pathbase=/relative-URL-path"
    
  • In Visual Studio, specify the argument in Properties > Debug > Application arguments. Setting the argument in the Visual Studio property page adds the argument to the launchSettings.json file.

    --pathbase=/relative-URL-path
    

URLs

The --urls argument sets the IP addresses or host addresses with ports and protocols to listen on for requests.

  • Pass the argument when running the app locally at a command prompt. From the app's directory, execute:

    dotnet run --urls=http://127.0.0.1:0
    
  • Add an entry to the app's launchSettings.json file in the IIS Express profile. This setting is used when running the app with the Visual Studio Debugger and from a command prompt with dotnet run.

    "commandLineArgs": "--urls=http://127.0.0.1:0"
    
  • In Visual Studio, specify the argument in Properties > Debug > Application arguments. Setting the argument in the Visual Studio property page adds the argument to the launchSettings.json file.

    --urls=http://127.0.0.1:0
    

Configure the Linker

Blazor performs Intermediate Language (IL) linking on each Release build to remove unnecessary IL from the output assemblies. For more information, see Configure the Linker for ASP.NET Core Blazor.

Custom boot resource loading

A Blazor WebAssembly app can be initialized with the loadBootResource function to override the built-in boot resource loading mechanism. Use loadBootResource for the following scenarios:

  • Allow users to load static resources, such as timezone data or dotnet.wasm from a CDN.
  • Load compressed assemblies using an HTTP request and decompress them on the client for hosts that don't support fetching compressed contents from the server.
  • Alias resources to a different name by redirecting each fetch request to a new name.

loadBootResource parameters appear in the following table.

Parameter Description
type The type of the resource. Permissable types: assembly, pdb, dotnetjs, dotnetwasm, timezonedata
name The name of the resource.
defaultUri The relative or absolute URI of the resource.
integrity The integrity string representing the expected content in the response.

loadBootResource returns any of the following to override the loading process:

  • URI string. In the following example (wwwroot/index.html), the following files are served from a CDN at https://my-awesome-cdn.com/:

    • dotnet.*.js
    • dotnet.wasm
    • Timezone data
    ...
    
    <script src="_framework/blazor.webassembly.js" autostart="false"></script>
    <script>
      Blazor.start({
        loadBootResource: function (type, name, defaultUri, integrity) {
          console.log(`Loading: '${type}', '${name}', '${defaultUri}', '${integrity}'`);
          switch (type) {
            case 'dotnetjs':
            case 'dotnetwasm':
            case 'timezonedata':
              return `https://my-awesome-cdn.com/blazorwebassembly/3.2.0/${name}`;
          }
        }
      });
    </script>
    
  • Promise<Response>. Pass the integrity parameter in a header to retain the default integrity-checking behavior.

    The following example (wwwroot/index.html) adds a custom HTTP header to the outbound requests and passes the integrity parameter through to the fetch call:

    <script src="_framework/blazor.webassembly.js" autostart="false"></script>
    <script>
      Blazor.start({
        loadBootResource: function (type, name, defaultUri, integrity) {
          return fetch(defaultUri, { 
            cache: 'no-cache',
            integrity: integrity,
            headers: { 'MyCustomHeader': 'My custom value' }
          });
        }
      });
    </script>
    
  • null/undefined, which results in the default loading behavior.

External sources must return the required CORS headers for browsers to allow the cross-origin resource loading. CDNs usually provide the required headers by default.

You only need to specify types for custom behaviors. Types not specified to loadBootResource are loaded by the framework per their default loading behaviors.

Change the filename extension of DLL files

In case you have a need to change the filename extensions of the app's published .dll files, follow the guidance in this section.

After publishing the app, use a shell script or DevOps build pipeline to rename .dll files to use a different file extension. Target the .dll files in the wwwroot directory of the app's published output (for example, {CONTENT ROOT}/bin/Release/netstandard2.1/publish/wwwroot).

In the following examples, .dll files are renamed to use the .bin file extension.

On Windows:

dir .\_framework\_bin | rename-item -NewName { $_.name -replace ".dll\b",".bin" }
((Get-Content .\_framework\blazor.boot.json -Raw) -replace '.dll"','.bin"') | Set-Content .\_framework\blazor.boot.json

If service worker assets are also in use, add the following command:

((Get-Content .\service-worker-assets.js -Raw) -replace '.dll"','.bin"') | Set-Content .\service-worker-assets.js

On Linux or macOS:

for f in _framework/_bin/*; do mv "$f" "`echo $f | sed -e 's/\.dll\b/.bin/g'`"; done
sed -i 's/\.dll"/.bin"/g' _framework/blazor.boot.json

If service worker assets are also in use, add the following command:

sed -i 's/\.dll"/.bin"/g' service-worker-assets.js

To use a different file extension than .bin, replace .bin in the preceding commands.

To address the compressed blazor.boot.json.gz and blazor.boot.json.br files, adopt either of the following approaches:

  • Remove the compressed blazor.boot.json.gz and blazor.boot.json.br files. Compression is disabled with this approach.
  • Recompress the updated blazor.boot.json file.

The preceding guidance also applies when service worker assets are in use. Remove or recompress wwwroot/service-worker-assets.js.br and wwwroot/service-worker-assets.js.gz. Otherwise, file integrity checks fail in the browser.

The following Windows example uses a PowerShell script placed at the root of the project.

ChangeDLLExtensions.ps1::

param([string]$filepath,[string]$tfm)
dir $filepath\bin\Release\$tfm\wwwroot\_framework\_bin | rename-item -NewName { $_.name -replace ".dll\b",".bin" }
((Get-Content $filepath\bin\Release\$tfm\wwwroot\_framework\blazor.boot.json -Raw) -replace '.dll"','.bin"') | Set-Content $filepath\bin\Release\$tfm\wwwroot\_framework\blazor.boot.json
Remove-Item $filepath\bin\Release\$tfm\wwwroot\_framework\blazor.boot.json.gz

If service worker assets are also in use, add the following command:

((Get-Content $filepath\bin\Release\$tfm\wwwroot\service-worker-assets.js -Raw) -replace '.dll"','.bin"') | Set-Content $filepath\bin\Release\$tfm\wwwroot\service-worker-assets.js

In the project file, the script is run after publishing the app:

<Target Name="ChangeDLLFileExtensions" AfterTargets="Publish" Condition="'$(Configuration)'=='Release'">
  <Exec Command="powershell.exe -command &quot;&amp; { .\ChangeDLLExtensions.ps1 '$(SolutionDir)' '$(TargetFramework)'}&quot;" />
</Target>

To provide feedback, visit aspnetcore/issues #5477.