dotnet / dotnet/aspnetcore

[Blazor] Service workers improvements

Open
#65,083 2 comments 1 reaction 0 assignees View on GitHub
area-blazor
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

This document outlines improvements to Blazor's service worker support to modernize the PWA experience, modernizing our implementation to take advantage of the latest available web standards and integrating with new functionality that we've built over the past releases and that we haven't taken advantage of. The proposal includes adopting ES6 module service workers, client-side content encoding negotiation for static hosting scenarios, fingerprint-based URL resolution for immutable caching, navigation preloading and so on.

## Motivation and Goals

The current Blazor PWA implementation uses legacy patterns that don't leverage modern browser capabilities:

1. **Legacy `importScripts()` pattern** - The current service worker uses `importScripts('./service-worker-assets.js')` which doesn't support ES6 modules and has cache management challenges.

2. **Unnecessary build complexity** - The `UpdateServiceWorkerFileWithVersion` task rewrites comment headers in the service worker for cache busting, but browsers handle service worker cache invalidation automatically.

3. **No SSR support** - Having a service worker is beneficial in other scenarios beyond webassembly.

4. **No standalone hosting support** - Blazor WASM apps hosted on static file servers (GitHub Pages, Azure Static Web Apps) cannot leverage compressed assets or fingerprinted URLs without server-side content negotiation.

### Goals

- Modernize service worker to use ES6 modules
- Simplify build pipeline by removing unnecessary header rewriting
- Integrate with Static Web Assets Endpoints for consistent asset handling
- Enable client-side content encoding negotiation for static file hosting scenarios
- Support fingerprint-based URL resolution for immutable caching
- **[Exploratory]** Support offline form submission with queuing and retry

## In Scope

### 1. ES6 Module Service Workers

**Browser Support**: ✅ All major browsers since 2021 (Chrome 91, Edge 91, Firefox 90, Safari 15)

Convert service workers to use ES6 modules via the `type: 'module'` option:

```javascript
// Registration
navigator.serviceWorker.register('/service-worker.js', {
type: 'module',
updateViaCache: 'none' // Never use HTTP cache for SW scripts
});
```

```javascript
// service-worker.js (ES6 module)
import { assets } from './service-worker-assets.js';

const CACHE_NAME = `blazor-cache-${assets.version}`;

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache =>
cache.addAll(assets.files.map(f => f.url))
)
);
});
```

### 2. updateViaCache: 'none' for Cache Bypass

Configure service worker registration to never use HTTP cache for the service worker script or imported scripts:

```javascript
navigator.serviceWorker.register('/service-worker.js', {
updateViaCache: 'none'
});
```

**Benefits**:
- Service worker updates are always fetched fresh from network
- Eliminates need for cache-busting version comments
- Simplifies the build pipeline (remove `UpdateServiceWorkerFileWithVersion` task)

### 3. Client-Side Content Encoding Negotiation and fingerprinting

**Browser Support**: Compression Streams API ✅ All major browsers (Chrome 80+, Edge 80+, Firefox 113+, Safari 16.4+)

Leverage the Static Web Assets Endpoints manifest to perform content encoding negotiation in the service worker for scenarios where we don't control the server (e.g., GitHub Pages, Azure Static Web Apps, other static file hosts).

#### Compression

When enabled via a flag, we will map requests from `resource.extension` to `resource.extension.gz` or `resource.extension.br` in the service worker. This way the app is capable of downloading the precompressed assets in standalone scenarios, which leads to significant savings in static hosting scenarios.

#### 3.1 Fingerprint Resolution

Map non-fingerprinted request paths to fingerprinted file paths, enabling long-term caching with immutable cache headers. We can fingerprint scripts and other assets, but there are situations (like CSS files) where we don't have the ability to rewrite the URLs in those documents.

### 4. Page Prefetching / Preloading / SW caching

A Blazor prefetching system that enables declarative and imperative page preloading to speed up navigations for predicted page transitions and client-side caching on the service worker. With the ability for components to provide link and the ability to define your own logic. The example here is to preload the next page on paginated grids to avoid the cost of the network latency and the db call. The cache on the SW is better than just the regular Cache-Control header as it more easily supports caching pages that we couldn't cache otherwise (like pages with user specific details).

### 5. Offline actions through "speculatory" execution.

The idea here is that a service worker can potentially not only cache the pages for read scenarios, but it can also support caching the speculative results of certain actions.

When a form is submitted and the app is offline, we can queue the form to submit it later when the app becomes offline. We can provide a different UI for offline scenarios that a service worker can cache as part of app installation.

Once the app becomes online, the developer can either decide to send all the queued posts automatically or provide a UI around managing the changes that happened while the app was offline.

One such UI could present the list of form posts as a "Pending changes" UI and to manually "apply/review" those changes. For example, when clicking on an item on the list, the app can send the form data in a request to "ask the server" to re-render the page with that form data, at which point the user can decide to submit the form. It also supports an experience where the app is in control of how it presents the UI in these scenarios.

## Out of Scope

TBD, but in general, anything not listed explicitly. We want to have some support for offline SSR, at least for read only scenarios, but form handling is something we would first experiment with.

## Risks and Unknowns

TBD

## Examples

TBD

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.