Blazor WebAssembly Goes GA

As announced in Build 2020, Blazor WebAssembly version 3.2.0 has officially made it to its first production release.

With this release Blazor WebAssembly is now officially supported, this means that we really can start using it for production scenarios.

One of the major advantages is that Blazor WebAssembly applications are completely client-side, so it’s not needed anymore to host it on a web-server. This feature opens up a world of new possibilities like hosting a Blazor WebAssembly application in Azure on a Storage Account or hosting the application in GitHub Pages. In this blog post I’ll focus on hosting the static web-page on GitHub Pages.

Blazor WebAssembly Application

First we need to start a new Blazor WebAssembly project using Visual Studio 2019: VisualStudio BlazorWebAssembly App

Now we need to make some adjustments to this application in order to be able to deploy and run it on GitHub Pages.

1. Edit Index.html

GitHub Pages doesn’t natively support single page apps. When there is a fresh page load for a url like example.tld/foo, where /foo is a frontend route, the GitHub Pages server returns 404 because it knows nothing of /foo.

So we need to add some javascript to the index.html page to work-around this logic, more details can be found here.

Replace the body tag in the index.html like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<body>
  <app>Loading...</app>

  <!-- Start Single Page Apps for GitHub Pages -->
  <script type="text/javascript">
    // Single Page Apps for GitHub Pages
    // https://github.com/rafrex/spa-github-pages
    // Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
    // ---
    // This script checks to see if a redirect is present in the query string
    // and converts it back into the correct url and adds it to the
    // browser's history using window.history.replaceState(...),
    // which won't cause the browser to attempt to load the new url.
    // When the single page app is loaded further down in this file,
    // the correct url will be waiting in the browser's history for
    // the single page app to route accordingly.
    (function(l) {
      if (l.search) {
        var q = {};
        l.search.slice(1).split('&').forEach(function(v) {
          var a = v.split('=');
          q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&');
        });
        if (q.p !== undefined) {
          window.history.replaceState(null, null,
            l.pathname.slice(0, -1) + (q.p || '') +
            (q.q ? ('?' + q.q) : '') +
            l.hash
          );
        }
      }
    }(window.location))
  </script>
  <!-- End Single Page Apps for GitHub Pages -->

  <div id="blazor-error-ui">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
  </div>
  <script src="_framework/blazor.webassembly.js"></script>
</body>

2. Add .nojekyll file

GitHub Pages uses Jekyll as static site generator to build Pages sites. This is equivalent to Hugo (which is used as static site generator for generating the mstack.nl website)

Because of the way that Jekyll works, any files or folders that start with any of these characters: _, ., # or end with ~ are not built.

The result is that folders as _framework, mandatory for a Blazor WebAssembly running project they aren’t processed.

To solve it, you must create a .nojekyll file under root of published files (The wwwroot-folder).

3. Add a 404.html page

By default, when the GitHub Pages server gets a request for a path defined with frontend routes, e.g. example.tld/foo, it returns a custom 404.html page.

The custom 404.html page below contains a script that takes the current url and converts the path and query string into just a query string, and then redirects the browser to the new url with only a query string and hash fragment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Single Page Apps for GitHub Pages</title>
    <script type="text/javascript">
      // Single Page Apps for GitHub Pages
      // https://github.com/rafrex/spa-github-pages
      // Copyright (c) 2016 Rafael Pedicini, licensed under the MIT License
      // ---
      // This script takes the current url and converts the path and query
      // string into just a query string, and then redirects the browser
      // to the new url with only a query string and hash fragment,
      // e.g. http://www.foo.tld/one/two?a=b&c=d#qwe, becomes
      // http://www.foo.tld/?p=/one/two&q=a=b~and~c=d#qwe
      // Note: this 404.html file must be at least 512 bytes for it to work
      // with Internet Explorer (it is currently > 512 bytes)
      // If you're creating a Project Pages site and NOT using a custom domain,
      // then set segmentCount to 1 (enterprise users may need to set it to > 1).
      // This way the code will only replace the route part of the path, and not
      // the real directory in which the app resides, for example:
      // https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
      // https://username.github.io/repo-name/?p=/one/two&q=a=b~and~c=d#qwe
      // Otherwise, leave segmentCount as 0.
      var segmentCount = 0;
      var l = window.location;
      l.replace(
        l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
        l.pathname.split('/').slice(0, 1 + segmentCount).join('/') + '/?p=/' +
        l.pathname.slice(1).split('/').slice(segmentCount).join('/').replace(/&/g, '~and~') +
        (l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') +
        l.hash
      );
    </script>
  </head>
  <body>
  </body>
</html>

GitHub Pages

GitHub Pages is a static site hosting service from GitHub that takes HTML, CSS, and JavaScript files straight from a repository on GitHub. You can host your site on GitHub’s github.io domain or your own custom domain.

This feature is very useful when you want to show a demo website showing some functionality for the Blazor component or project you’ve build. By default this demo website will be hosted on the github.io domain.

Create gh-pages branch

Before continuing, we need to create a separate branch from which the GitHub Pages will be served. Use the commands below to create a new branch named gh-pages and commit this to your GitHub repository.

1
2
3
git branch gh-pages
git branch gh-pages
git push --set-upstream origin gh-pages

Note that you can also use your favorite code editor (Visual Studio or Code) to create a new branch, commit and push.

Enable GitHub pages on your project

To enable the static hosting service on your repository, go to the Settings and scroll down to GitHub Pages and select for the Source the just created branch: GitHub Settings Pages

Now GitHub can host the static website, based on the files in this branch, however this branch does not yet contain any files, this will be implemented by the GitHub Actions.

GitHub Actions

GitHub Actions makes it easy to automate all your software workflows using CI/CD pipelines. Building, testing, and deploying your code right from GitHub.

Scenario

Our scenario is as follows: when new (example) code is checked in to the master branch, the Blazor WebAssembly example application should be build automatically and the updated files (.html, .js, .css and images) should be pushed to the gh-pages branch.

To achieve this, we need to create a new workflow from scratch:

GitHub Actions New WorkFlow GitHub Actions Starter WorkFlow

A new editor is displayed where the steps should be defined in YAML.

The complete action looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# A. Define the name for this action
name: DeployToGitHubPages

# B. Define a environment variable to indicate the publish location from the Blazor WebAssembly App
env:
  PUBLISH_DIR: examples/Howler.Blazor-WASM-AudioPlayer/bin/Release/netstandard2.1/publish/wwwroot

# C. Controls when the action will run, in this case only checkins on master branch
on:
  push:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    # D. Check out the code
    - uses: actions/checkout@v2

    # E. Build and Publish the Blazor WebAssembly App
    - name: Publish app
      run: dotnet publish -c Release examples/Howler.Blazor-WASM-AudioPlayer

    # F. The <base> url in index.html needs to be modified depending on where the project is deployed.
    - name: Rewrite base href
      uses: SteveSandersonMS/ghaction-rewrite-base-href@v1
      with:
        html_path: ${{ env.PUBLISH_DIR }}/index.html
        base_href: /Howler.Blazor/

    # G. This step commits all files from the wwwroot folder to the gh-pages branch
    - name: GitHub Pages
      if: success()
      uses: crazy-max/ghaction-github-pages@v1.5.1
      with:
        target_branch: gh-pages
        build_dir: ${{ env.PUBLISH_DIR }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Now commit this Action to the master branch and the new CI-CD will start running on your GitHub project: GitHub Actions All WorkFlows.

Once the action is complete, you should be able to browse to the Blazor WebAssembly application hosted on github.io.

Conclusion

Developing and building Blazor applications or components and hosting an example WebAssembly Application is now very easy using new Blazor WebAssembly, combined with GitHub Pages and GitHub Actions. This blog is just a start, you can use this as a base and extend it to your own needs.

Demo projects

For some demo projects which using this flow, see: