OverrideHtmlAssetPlaceholders breaks hot reload during dotnet watch in Blazor WebAssembly
- 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.