Blazor WebAssembly to PWA: Step-by-Step Guide

From Web to Installable App: Turning Your Blazor WebAssembly Project into a Powerful PWA

Are you just building websites or creating real apps that live on your user’s devices? Progressive Web Apps (PWAs) blur that line—and Blazor WebAssembly makes that leap easier than ever for .NET developers. Let’s explore how you can take your Blazor WebAssembly project and turn it into a fully installable, offline-capable PWA.

Understanding PWAs and Blazor WebAssembly

What is a Progressive Web App (PWA)?

PWAs are web applications that behave like native mobile or desktop apps. They combine the best of both worlds: the reach of the web and the experience of an app.

Core characteristics of a PWA:

  • Offline Capability: Works without an internet connection using cached resources.
  • Installability: Can be added to a device’s home screen or app drawer.
  • Responsiveness: Fits all form factors from phones to desktops.
  • App-like Behavior: Launches full-screen and integrates with device features.

Introduction to Blazor WebAssembly

Blazor WebAssembly is a front-end framework that lets you build interactive web UIs using C# instead of JavaScript. It runs .NET directly in the browser via WebAssembly.

Why it’s ideal for PWAs:

  • Built-in support for PWA templates.
  • Full .NET ecosystem compatibility.
  • Great performance with native-like interactivity.

Setting Up Your Blazor WebAssembly App as a PWA

Creating a Blazor PWA Project

Start with the Blazor WebAssembly PWA template:

 dotnet new blazorwasm -o MyPwaApp --pwa

This command scaffolds a new project with all necessary files for a PWA: manifest, service worker, and offline support.

To upgrade an existing Blazor app manually:

  1. Add wwwroot/manifest.webmanifest.
  2. Add wwwroot/service-worker.js and service-worker.published.js.
  3. In index.html, include:
<link rel="manifest" href="manifest.webmanifest">
<script src="service-worker.js"></script>
  1. Register the service worker in Program.cs:
builder.Services.AddPwaServices();

Enabling the Web Manifest

The manifest.webmanifest file tells the browser about your app’s appearance and behavior when installed.

{
  "name": "My PWA App",
  "short_name": "PWAApp",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0000ff",
  "icons": [
    {
      "src": "icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

This configuration controls the install prompt visuals and defines the launch experience.

Understanding the Service Worker

Blazor uses service-worker.published.js to cache essential resources and ensure offline operation.

Common pattern:

const CACHE_NAME = "blazor-cache-v1";
const urlsToCache = ["/", "/index.html", "/css/app.css"];

self.addEventListener("install", event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(urlsToCache))
  );
});

This caches essential files during installation so they can load even when offline.

Implementing PWA Features in Blazor

Adding Offline Support

Handle offline scenarios gracefully:

  • Add /wwwroot/offline.html for when the app fails to load.
  • Modify service-worker.js to redirect requests:
self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request).catch(() => caches.match('/offline.html'))
  );
});
  • Use try/catch for API calls in components:
try
{
    var data = await Http.GetFromJsonAsync<MyData>("/api/data");
}
catch (HttpRequestException)
{
    data = await localStorage.GetItemAsync<MyData>("cachedData");
}

Caching Strategies Explained

There are two types of caching:

  • Pre-caching: Static assets like .dll, .wasm, .css are cached at build time.
  • Runtime caching: Dynamic requests (like /api/data) cached during runtime.

Customize caching:

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

Updating the Service Worker Safely

Control updates with versioning:

const CACHE_VERSION = 'v2';
const CURRENT_CACHES = {
  prefetch: 'blazor-cache-' + CACHE_VERSION
};

Notify users with a banner:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.oncontrollerchange = () => {
    alert('New version available. Please refresh.');
  };
}

Testing and Installing Your Blazor PWA

Testing PWA Features in DevTools

Steps in Chrome DevTools:

  1. Open DevTools > Application tab.
  2. Check for valid manifest and service worker.
  3. Use the “Offline” checkbox to simulate loss of connectivity.
  4. Inspect cached files under “Cache Storage”.

Bonus: Use Lighthouse audit to verify PWA score and identify issues.

Installation Experience Across Devices

Installation triggers vary by OS/browser:

  • Desktop (Chrome/Edge): Shows install icon in address bar.
  • Android Chrome: Prompts after two visits.
  • iOS Safari: Manual install via Share > Add to Home Screen.

Tips:

  • Include clear instructions for users.
  • Use beforeinstallprompt event to show custom banners.
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  showInstallBanner();
});

Going Further with Advanced PWA Techniques

Push Notifications with Blazor PWAs

Enable push in service worker:

self.addEventListener('push', event => {
  const data = event.data.json();
  self.registration.showNotification(data.title, {
    body: data.body,
    icon: 'icon-192.png'
  });
});

In Blazor, request permission:

await JS.InvokeVoidAsync("Notification.requestPermission");

Use a server like ASP.NET with Web Push libraries to send notifications.

Background Sync and Advanced APIs

Support retrying failed tasks:

self.addEventListener('sync', function(event) {
  if (event.tag === 'sync-posts') {
    event.waitUntil(syncFailedPosts());
  }
});

Store data locally using IndexedDB:

const dbRequest = indexedDB.open("AppData", 1);
dbRequest.onupgradeneeded = event => {
  let db = event.target.result;
  db.createObjectStore("posts", { keyPath: "id" });
};

Use the File System Access API (desktop only) for enhanced local file handling.

FAQ: Common Questions About Blazor PWAs

Do Blazor PWAs work offline completely?

Only if all assets and critical data are cached or stored locally.

Can I use SignalR in a PWA?

Yes, but only when online. Offline fallback logic must be handled separately.

Are updates automatically applied?

Not always. Use service worker lifecycle events to control update behavior.

Conclusion: From Website to Real App—Powered by Blazor

With just a few files and configurations, your Blazor WebAssembly site can become a PWA—giving users the power to install, use offline, and get near-native experiences. The best part? You stay in the C# world, with full access to the .NET ecosystem.

Try transforming your next project into a PWA—and let me know how it goes in the comments below. Got a cool use case? Share it!

Leave a Reply

Your email address will not be published. Required fields are marked *