Deploying a Blazor site to GitHub Pages

8 January 2025

How to create a Blazor WebAssembly website using Tailwind CSS and deploy it to GitHub Pages.

As a brief interlude to my ProcGen Fun series, I'd like to write up how I created a playground website which allows you to run the code I've been writing in that series.

The procedural generation is all being written in C#, so we need a website which can run .NET code (directly or indirectly). For many years the way to do this would be to build a static website (using HTML, CSS and JavaScript) which called a web API implemented in .NET. However, with the recent advent of WebAssembly (WASM) we can now run binary code client-side. Blazor is the .NET technology which uses WASM to run .NET code in the browser, using a templating syntax called Razor.

In my case, I've built a static website using Blazor, using Tailwind CSS for styling, and deployed it to GitHub Pages. What follows is a list of steps that you can follow, based on what I did. I hope some or all of it is helpful!

1. Add a blank Blazor project

In Visual Studio, create a new blank Blazor project. You can do this using "Add New Project" and selecting the "Blazor WebAssembly App Empty" template. You can also do this via the command line, using the following command.

dotnet new blazorwasm-empty -o YourProjectName

The reason I suggest using the "empty" template is that the standard template comes with Bootstrap CSS pre-installed, and we want to use Tailwind CSS.

2. Upgrade to the latest .NET

For some reason, on my machine step 1 generated a Blazor project targetting .NET 7, which is out of support. If this happens to you, I suggest upgrading to .NET 8 (the latest LTS version of .NET at the time of writing).

To do this, change the TargetFramework property in the project file to net8.0, and upgrade the two Blazor NuGet packages to the latest v8 release.

3. Add Tailwind

The easiest way to add Tailwind CSS to a project is to install it using npm, and then use the init command to create a blank config file.

npm install -D tailwindcss
npx tailwindcss init

In your newly-created config file, set the template paths (the content setting) as follows. This tells Tailwind where to look for class names when compiling the CSS.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./**/*.{razor,html}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

Then create your main CSS file. I called mine tailwind.css and put it in the Styles directory.

@tailwind base;
@tailwind components;
@tailwind utilities;

The build script we're going to use (see below) will compile this file and output the result to css/tailwind.css. So in your index.html file, add a reference to this path in the head:

<!DOCTYPE html>
<html lang="en">

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

<body>
  ...
</body>
</html>

Now we can use Tailwind classes in our HTML files and Razor components.

For more information on how to configure Tailwind, see the official docs.

4. Add deployment to GitHub Pages

There are a few things which need to be done before you can publish your site to GitHub Pages. Fortunately there is a NuGet package called PublishSPAforGitHubPages.Build which does lots of the work for you! I suggest having a read of the documentation to understand what it does.

You can install it using this command.

dotnet add package PublishSPAforGitHubPages.Build

Then you need a GitHub Actions script, to be run on every push to your main branch. This should be placed in a YAML file (I called mine gh-pages.yml) in the .github/workflows folder of your repository.

Here's the script I used. If you use this script then you might need to change various branch names and paths to match your setup.

name: GitHub Pages Deployment

on:
  push:
    branches:
      - main

jobs:
  deploy:
    permissions:
      contents: write
    runs-on: ubuntu-latest
    steps:
      # Checkout the code
      - uses: actions/checkout@v4

      # Install .NET SDK
      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 8.0.x

      # Install NPM dependencies
      - name: Install NPM dependencies
        working-directory: ./ProcGenFun.Blazor
        run: npm i

      # Compile Tailwind CSS
      - name: Compile Tailwind CSS
        working-directory: ./ProcGenFun.Blazor
        run: npx tailwindcss -i ./Styles/tailwind.css -o ./wwwroot/css/tailwind.css

      # Publish the site
      - name: Publish
        run: dotnet publish ./ProcGenFun.Blazor/ProcGenFun.Blazor.csproj -c:Release -o:publish -p:GHPages=true

      # Deploy the site
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: publish/wwwroot
          force_orphan: true
          publish_branch: gh-pages

The key thing here is that we compile the Tailwind CSS before compiling the Blazor project, so that the CSS output is included in the compiled bundle. We then push this bundle in a single orphaned commit to the gh-pages branch.

The final step is to set up GitHub Pages to deploy from the gh-pages branch. This means that every time the gh-pages branch changes (i.e. when our script is run), GitHub Pages will update our public website to match the contents of the repository on that branch. In the GitHub web UI, go to Settings (for the repository), click on Pages, and then under "branch" select gh-pages.

5. Add custom domain

Your site will now be deployed at {username}.github.io/{repository} (e.g. mine was available at djcarter85.github.io/ProcGenFun). Often you'll want to use a custom domain instead; for example, I have published mine at procgenfun.carterdan.net.

The GitHub docs for this are reasonably clear (including how to configure the settings with your DNS provider), but it's worth noting that if you use the GitHub web UI to configure the custom domain, it'll make a change on your gh-pages branch which will be overwritten the next time the GitHub Actions script is run. Instead, add the CNAME file yourself in the wwwroot directory of your Blazor project, specifying your desired custom domain name on a single line. This will then be placed in the right location within your published bundle.

As a final step, you'll need to set the site's base path in your build script. The NuGet package referenced above assumes you're not using a custom domain name, and sets the base path to /{repository}. You can set the base path back to / by adding the GHPagesBase parameter when publishing the site in your GitHub Actions script:

...
      # Publish the site
      - name: Publish
        run: dotnet publish ./ProcGenFun.Blazor/ProcGenFun.Blazor.csproj -c:Release -o:publish -p:GHPages=true -p:GHPagesBase=/
...

Appendix: development

The easiest way I found to develop using this setup is to use VSCode. In one terminal I set up Tailwind to automatically recompile the CSS when anything changes.

cd ProcGenFun.Blazor
npx tailwindcss -i .\Styles\tailwind.css -o .\wwwroot\css\tailwind.css --watch

And then in another terminal I started the Blazor project, using the watch command to reload the site when anything changes.

cd ProcGenFun.Blazor
dotnet watch

This gives you fast feedback on the changes you make as you're developing your site.

Further reading

Here's a couple of related blog posts which I found helpful.