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:
- Add
wwwroot/manifest.webmanifest
. - Add
wwwroot/service-worker.js
andservice-worker.published.js
. - In
index.html
, include:
<link rel="manifest" href="manifest.webmanifest">
<script src="service-worker.js"></script>
- 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:
- Open DevTools > Application tab.
- Check for valid manifest and service worker.
- Use the “Offline” checkbox to simulate loss of connectivity.
- 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
Only if all assets and critical data are cached or stored locally.
Yes, but only when online. Offline fallback logic must be handled separately.
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!