Running Your ASP.NET Core Blazor WebAssembly Application As A Progressive Web App (PWA)

As already outlined in my previous article, a Blazor application almost always has to integrate with the JavaScript world, and especially with the DOM layer in the browser. In this article, I am going to quickly show you how to transform your Blazor WebAssembly application into a Progressive Web App (PWA). You will see that it is quite easy to achieve - but you will also see and we will prove that we always need a good share of Web, browser and JavaScript knowledge when working with Blazor.

In diesem Artikel:

Christian Weyer
Christian Weyer ist Mitbegründer und CTO von Thinktecture. Er ist seit mehr als zwei Jahrzehnten in der Softwarebranche aktiv.

Note: In this article I am not talking about Blazor Server-side, but only the Client-side hosting model based on WebAssembly. However, the proposed process to get to a Blazor PWA should also work with Blazor Server.

Version information

The data and code in this article are based on these technology versions:
ASP.NET Core Blazor: 3.2.0-preview1.20073.1

Progressive Web Apps (PWA) - modern apps

The Web is ever-evolving and developers and users get the chance to build very native-like applications running in the browser based on the Progressive Web Apps (PWA) Uber-pattern. E.g. PWAs can be installed, offer an improved startup experience and can also run logic in the background, like being able to receive push notifications. All in all, I think the future for modern applications running in and on the Web is bright – and I would like your Blazor applications to participate.

We are not going to cover the basics of PWAs in this article, there are numerous resources out there to get you started.

A proven fact is that you as a Blazor developer will not be able to write the necessary PWA-related artifacts (like application manifest or the service worker implementation) in C#: this will all stay and happen in the JavaScript side of the world. Period.

To prepare your application on its way to PWA goodness, adjust your index.html to include the necessary meta tags (especially the link to the application manifest, manifest.json in our case) as well as the inclusion of the service worker JavaScript file (this will be generated later on with a tool):

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

    <meta charset="utf-8" />
    <title>Conf List</title>
    <base href="." />

    <meta name="description" content="Simple conference list app.">

    <link rel="manifest" href="manifest.json" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="theme-color" content="#1294e0">
    <link rel="apple-touch-icon" href="icon-192.png">
    <link rel="icon" href="favicon.ico" type="image/x-icon" />

    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="_content/MatBlazor/dist/matBlazor.css" rel="stylesheet" />


    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>

    <script src="_framework/blazor.webassembly.js"></script>
    <script src="_content/MatBlazor/dist/matBlazor.js"></script>

<script src="" data-minify="1"></script></body>



Application Manifest

The application manifest is the starting point to all-things-PWA. Here we define the application name, short name, the icons to be used, and colors to be applied to our PWA when running. In addition, we are specifying the start URL and the scope of the application, two values that are important for the service worker to work correctly:

  "short_name": "ConfTool",
  "name": "Conferences Tool",
  "icons": [
      "src": "icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
  "start_url": "/",
  "background_color": "#1294e0",
  "display": "standalone",
  "scope": "/",
  "theme_color": "#ff584f"

Service Worker with Workbox

While we can always write our service worker by hand, we actually shouldn’t do that. There are really good tools in the community to generate your service worker files which include a high configurability to get various things done. My favorite tool is Workbox, which also has a CLI.

The Workbox CLI takes a configuration file to generate the service worker file for us.

					module.exports = {
    globDirectory: "bin/Release/netstandard2.1/publish/BlazorConfListPWA/dist",
    globPatterns: [
    swDest: "bin/Release/netstandard2.1/publish/BlazorConfListPWA/dist/sw.js",
    navigateFallback: "/BlazorPWA/index.html",
    clientsClaim: true,
    runtimeCaching: [{
        urlPattern: "",
        handler: "NetworkFirst",
        options: {
            cacheName: "conferencesApi",
            expiration: {
                maxAgeSeconds: 100,

Here we are point Workbox to the output of the Blazor publish bundle, and we include all necessary files in the globPatterns. This leads to a service worker file listing all those files in an array which makes it possible to already cache the application files upon the first run of the service worker – thus making the app immediately available offline.

When we execute workbox generateSW in the path of the above config file, we get two files: sw.js and workbox-[some-hash].js.

In the sw.js we can see the explicitly listed files to be included in the application cache:

					define("./sw.js", ["./workbox-fa9ea827"], (function(e) {
    "use strict";
    self.addEventListener("message", e => { && "SKIP_WAITING" === && self.skipWaiting()
    }), e.clientsClaim(), e.precacheAndRoute([{
        url: "_content/MatBlazor/dist/matBlazor.css",
        revision: "f887f122dea68e52fd151f7d348b006b"
    }, {
        url: "_content/MatBlazor/dist/matBlazor.js",
        revision: "c92e6b8a42cd4ef811c1d99d01f6aaa6"
    }, {
        url: "_framework/_bin/BlazorConfListPWA.dll",
        revision: "f1a09d67416f4b0aa58287750c636ebf"
    }, {
        url: "_framework/_bin/MatBlazor.dll",
        revision: "68906b5c0f400cd89516c8765b43d3a4"
    }, {
        url: "_framework/_bin/Microsoft.AspNetCore.Blazor.dll",
        revision: "954c481979e2a46f787d27a649134553"
    }, {
        url: "_framework/_bin/Microsoft.AspNetCore.Blazor.HttpClient.dll",
        revision: "b04448036efb43ad5bdc0365fd429886"

Another important section in the config file above is the runtimeCaching part. By using a NetworkFirst strategy we can cache the response of our HTTPS API calls. In our case, the response data will be put into its cache and the data will be cached for 100 seconds.

OK, with the index.hxml, the application manifest and the service worker files in place, we can now run our Blazor application. There will be this small ‚+‘ symbol in the URL bar which allows us to install the Blazor application as a PWA – in this case via Chrome on Windows:

Chrome Audits with Blazor applications

Last but not least, now that we have a basic PWA running, it would be great to see how it behaves when being profiled by tooling.

Running Lighthouse for a desktop application will result in this:

Not too bad! The 92 for Accessibility comes from the fact that for this sample the background and foreground colors do not have a sufficient contrast ratio – and low-contrast text is difficult or impossible for many users to read. The other categories show full 100 points.

Let’s dive a bit into the Progressive Web Apps section of the Lighthouse report:

As you can see everything is awesome.

Well, almost. There is this performance-related error: this is actually a bug in Lighthouse. The time-to-interactive in our Blazor application is not 27 seconds 😉 It is more like 2.7 seconds. I will go ahead and file an issue with the Lighthouse team on this.

We can see that the de-facto PWA analyzer tool is very happy with our Blazor PWA.


Here we are: a Progressive Web App with an application manifest, a service worker caching the application files and providing also a cache for calling the HTTPS APIs from inside the application – all based on our ASP.NET Core Blazor C# code.

The source code for the demo application can be found here:


Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Low-angle photography of metal structure

AI-Funktionen zu Angular-Apps hinzufügen: lokal und offlinefähig

Künstliche Intelligenz (KI) ist spätestens seit der Veröffentlichung von ChatGPT in aller Munde. Wit WebLLM können Sie einen KI-Chatbot in Ihre eigenen Angular-Anwendungen integrieren. Wie das funktioniert und welche Vor- und Nachteile WebLLM hat, lesen Sie hier.

gRPC Code-First mit ASP.NET Core 7 und Blazor WebAssembly

Wie in allen anderen browserbasierten Single-Page-Application (SPA) Frameworks, ist Blazor WebAssembly JSON-over-HTTP (über Web- oder REST-APIs) die bei weitem häufigste Methode, um Daten auszutauschen und serverseitige Vorgänge auszulösen. Der Client sendet eine HTTP-Anfrage mit JSON-Daten an eine URL, mitunter über unterschiedliche HTTP-Verben. Anschließend führt der Server eine Operation aus und antwortet mit einem HTTP-Statuscode und den resultierenden JSON-Daten. Warum sollte man das ändern? Nun, es gibt Gründe - vor allem wenn man in einem geschlossenen System ist und .NET sowohl im Frontend als auch im Backend einsetzt.

Blazor WebAssembly in .NET 7: UI-Performance-Optimierung auf Komponentenebene

Stockende UI, keine Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld - dies sind nur wenige Beispiele alltäglicher Probleme, die der Nutzung von Client-Anwendungen im Allgemeinen, und bei Webanwendungen im Speziellen, immer wieder auftreten können. In diesem Artikel schauen wir uns an, wie wir komponentenbasierte UIs in Blazor WebAssembly optimieren können, um dadurch eine für die Benutzer zufriedenstellende Geschwindigkeit und ein flüssiges UI zu bekommen.

Understanding and Controlling the Blazor WebAssembly Startup Process

There are a lot of things going on in the background, when a Blazor WebAssembly application is being started. In some cases you might want to take a bit more control over that process. One example might be the wish to display a loading screen for applications that take some time for initial preparation, or when users are on a slow internet connection. However, in order to control something, we need to understand what is happening first. This article takes you down the rabbit hole of how a Blazor WASM application starts up.

Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Blazor WebAssembly is a powerful framework for building web applications that run on the client-side. With Project Fugu APIs, you can extend the capabilities of these apps to access new device features and provide an enhanced user experience. In this article, learn about the benefits of using Project Fugu APIs, the wrapper packages that are available for Blazor WebAssembly, and how to use them in your application.

Whether you're a seasoned Blazor developer or just getting started, this article will help you add superpowers to your Blazor WebAssembly app.

Blazor WebAssembly in Practice: Maturity, Success Factors, Showstoppers

ASP.NET Core Blazor is Microsoft's framework for implementing web-based applications, aimed at developers with knowledge of .NET and C#. It exists alongside other frameworks such as ASP.NET Core MVC. About two and a half years after the release of Blazor WebAssembly and based on our experiences from many customer projects at Thinktecture, we want to have a close look at the following questions: What is the current state of the framework? How can you successfully use Blazor? And where does it have limitations?