Building Offline‑First Progressive Web Apps with .NET 8 Blazor WebAssembly and Native AOT

Mahmut Sarıkaya 4 min read 1 Views 0
Building Offline‑First Progressive Web Apps with .NET 8 Blazor WebAssembly and Native AOT

Why offline‑first matters

Imagine a commuter checking a transit app while a subway tunnel cuts the cellular signal. If the app cannot function without a network, the user is left stranded. Statistics from Google show that 53% of mobile users abandon a site that takes longer than three seconds to load, and 30% of those abandon after a single failed request. An offline‑first Progressive Web App (PWA) eliminates that friction by caching assets, data, and UI logic locally, guaranteeing a responsive experience regardless of connectivity.

Setting up .NET 8 and Blazor WebAssembly

Before any code is written, verify the development environment. .NET 8 was released in November 2023 and includes built‑in support for Native AOT. The minimum requirement is Windows 10 version 1903, macOS 12, or a recent Linux distribution with glibc 2.28+. Install the SDK and create a new Blazor WebAssembly project with the PWA template:

dotnet new install Microsoft.AspNetCore.Components.WebAssembly.Templates
dotnet new blazorwasm -o OfflineFirstApp -f net8.0 --pwa

The command generates a solution that already contains a service‑worker.js file, a manifest.webmanifest, and the necessary NuGet packages.

Enabling Progressive Web App features

The generated project includes a service-worker.published.js that caches static files. To make the app truly offline‑first, extend the caching strategy to API responses. Add a fetch handler that stores JSON payloads in IndexedDB using the idb‑keyval library. The following JavaScript snippet registers the service worker and defines a runtime caching rule for requests that start with /api/:

if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js').then(reg => { console.log('SW registered', reg); }); } self.addEventListener('fetch', event => { const url = new URL(event.request.url); if (url.pathname.startsWith('/api/')) { event.respondWith(caches.open('api-cache').then(cache => { return cache.match(event.request).then(response => { return response || fetch(event.request).then(networkResp => { cache.put(event.request, networkResp.clone()); return networkResp; }); }); })); } });

Because the service worker runs in a separate thread, it does not block the UI, and the caching logic works both in development and after publishing.

Compiling with Native AOT

Native AOT produces a single, self‑contained executable that starts up in under 200 ms on a typical laptop—far faster than the default JIT compilation. To enable it, edit the project file (OfflineFirstApp.csproj) and add the following property group:


true
win-x64
true

Then publish with the AOT flag:

dotnet publish -c Release -r win-x64 /p:PublishAot=true

The output folder contains OfflineFirstApp.exe and a trimmed set of native libraries. When the user launches the PWA from a desktop shortcut, the native host boots instantly, and the WebAssembly runtime loads the pre‑compiled DLLs, delivering near‑native performance.

Testing offline functionality

Chrome DevTools offers a reliable way to simulate offline conditions. Open the Application tab, enable “Offline” in the Service Worker section, and reload the page. All UI elements should render from the cache, and any API call to /api/ should be served from IndexedDB. Additionally, use the “Lighthouse” audit to verify that the PWA meets the 100‑point offline criteria.

Automated testing can be added with Playwright. The following script navigates to the app, forces offline mode, and asserts that a known element is still visible:

const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch(); const context = await browser.newContext(); const page = await context.newPage(); await page.goto('https://localhost:5001'); await context.setOffline(true); await page.waitForSelector('h1'); console.log('Offline UI loaded'); await browser.close(); })();

Performance tips for an offline‑first PWA

1. Keep the service‑worker cache size under 50 MB to avoid storage quota errors on mobile browsers.
2. Use HTTP/2 server push for critical assets during the first load; the push will be cached automatically.
3. Trim the Blazor payload by disabling unused components in Program.cs and setting LinkerEnabled=true in the publish profile.
4. Monitor runtime metrics with dotnet-counters to ensure the AOT binary stays under the 30 MB memory ceiling recommended for low‑end devices.

Conclusion

Combining .NET 8’s Native AOT with Blazor WebAssembly gives developers a powerful toolkit for building offline‑first PWAs that feel as snappy as native apps. By configuring a robust service worker, leveraging IndexedDB for data persistence, and publishing an AOT‑compiled binary, you can deliver a consistent experience across browsers, desktops, and mobile devices. The result is a web application that not only survives network interruptions but also starts up in a fraction of a second—exactly what modern users expect.

Sources

Microsoft Docs – .NET 8 Native AOT
Microsoft Docs – Blazor WebAssembly PWA template
Google Developers – Progressive Web App Checklist

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #.NET 8 #Blazor WebAssembly #Progressive Web App #Native AOT #offline‑first
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

6 + 2 =