dotnet / dotnet/sdk

OverrideHtmlAssetPlaceholders breaks hot reload during dotnet watch in Blazor WebAssembly

Open
#53,331 2 comments 0 reactions 1 assignee Claimed by @maraf View on GitHub
Area-AspNetCore Area-Watch
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

## Is there an existing issue for this?

- [x] I have searched the existing issues

## Describe the bug

When `OverrideHtmlAssetPlaceholders` is set to `true` in a standalone Blazor WebAssembly project and `index.html` contains `#[.{fingerprint}]` placeholders (as documented for asset fingerprinting), hot reload via `dotnet watch` stops working. Code changes are detected and deltas are sent to the browser, but they are never applied — the page does not update.

### Root cause

Setting `OverrideHtmlAssetPlaceholders=true` automatically sets `BlazorFingerprintBlazorJs=true` (in `Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets`, line 58). This causes `blazor.webassembly.js` to be fingerprinted with a content hash (e.g., `blazor.webassembly.k8lfu09m0y.js`).

During `dotnet watch`, each rebuild produces a **different fingerprint hash**, which changes the script filename in the processed `index.html`. The hot reload infrastructure (`aspnetcore-browser-refresh.js`) delivers deltas successfully over the WebSocket connection, but the delta application mechanism fails because the entry point script filename has changed between the initial page load and the rebuild.

The browser console shows deltas being received but never applied. No error is shown — hot reload silently fails.

### Key finding from investigation

Through systematic isolation testing we confirmed:

| Configuration | Hot reload |
|---|---|
| `OverrideHtmlAssetPlaceholders=true` + `#[.{fingerprint}]` in index.html | **Broken** |
| `OverrideHtmlAssetPlaceholders=true` + plain `blazor.webassembly.js` in index.html | Works |
| `OverrideHtmlAssetPlaceholders` not set + plain `blazor.webassembly.js` | Works |

The fingerprint **placeholder in the HTML** combined with JS fingerprinting is the specific combination that breaks hot reload. The `OverrideHtmlAssetPlaceholders` property alone (without the placeholder in HTML) does not cause the issue.

## To Reproduce

1. Create a new Blazor WebAssembly Standalone app:
```
dotnet new blazorwasm -o BlazorHotReloadRepro
```

2. Add `OverrideHtmlAssetPlaceholders` to the `.csproj`:
```xml

true

```

3. Update `wwwroot/index.html` to use the fingerprint placeholder on the Blazor script tag:
```html

```

4. Run with `dotnet watch`:
```
dotnet watch run
```

5. Open the app in the browser, navigate to a page.

6. Make a visible change to a `.razor` file (e.g., change text in `Home.razor`).

7. **Expected:** The change appears in the browser via hot reload.
**Actual:** The change is never applied. The browser console shows hot reload deltas received but not applied.

8. To confirm the fix, add this to the `.csproj`:
```xml

false

```
Restart `dotnet watch` — hot reload now works.

## Suggested fix

The SDK should automatically set `BlazorFingerprintBlazorJs=false` when `DOTNET_WATCH=1`. This is consistent with how other watch-specific optimizations are already handled (e.g., the SDK disables documentation generation during watch builds).

In `Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets`, the existing line:

```xml
true
```

Could be updated to:

```xml
true
```

The `OverrideHtmlAssetPlaceholders` task with `IncludeOnlyHardFingerprintedModules=false` (used for build) correctly resolves `#[.{fingerprint}]` to an empty string when the asset isn't fingerprinted, so the HTML output is correct with just this property change.

## Workaround

Add to `Directory.Build.props` (or individual `.csproj`):

```xml

false

```

This disables JS fingerprinting in Debug builds, preserving hot reload for both `dotnet watch` and Visual Studio. Release/Publish builds retain full fingerprinting.

## Exceptions (if any)

No exceptions thrown. Hot reload silently fails — deltas are received but not applied.

## .NET Version

10.0.100-preview.5 (also reproducible on 10.0.200-preview.0.26103.119)

## Anything else?

- The `OverrideHtmlAssetPlaceholders` task itself works correctly — when `BlazorFingerprintBlazorJs=false`, it resolves `#[.{fingerprint}]` to empty string, producing clean `blazor.webassembly.js` references.
- The issue also affects Visual Studio hot reload, not just `dotnet watch`.
- Stale build artifacts can compound the issue — cleaning the artifacts directory is sometimes needed after toggling the property.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.