Symptom
Any Blazor Server app with prerendering enabled that registers the library's services (AddPanoramicDataBlazor() or AddNavigationCancelService()) logs this on every page load:
TaskScheduler.UnobservedTaskException fired.
System.AggregateException: ... (JavaScript interop calls cannot be issued at this time. This is because the
component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be
performed during the OnAfterRenderAsync lifecycle method.)
---> System.InvalidOperationException ...
at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(...)
Observed in MagicSuite's Ops Dashboard (MS-25810) with PanoramicData.Blazor 10.0.205; attributed with a FirstChanceException stack capture, which points directly at the NavigationCancelService constructor.
Cause
Services/NavigationCancelService.cs starts a JS interop call in a field initialiser:
private readonly Task<IJSObjectReference>? _loadCommonJsTask =
jsRuntime.InvokeAsync<IJSObjectReference>("import", JSInteropVersionHelper.CommonJsUrl).AsTask();
The service is scoped and injected into PDNavLink/PDForm, so during the static prerender pass a fresh instance is constructed per request. RemoteJSRuntime is not yet attached to a circuit, the task faults immediately, and nothing observes it unless ProceedAsync happens to run — so the finalizer reports it as an UnobservedTaskException, once per page load. Because the fault arrives via the finalizer, the stack never identifies the component, which makes it expensive for consumers to attribute.
Suggested fix
Load the module lazily on first use instead of at construction — by the time a listener cancels a navigation the circuit is interactive:
private Task<IJSObjectReference>? _loadCommonJsTask;
public async Task<bool> ProceedAsync(string target)
{
var args = new BeforeNavigateEventArgs { Target = target };
BeforeNavigate?.Invoke(this, args);
if (args.Cancel)
{
_loadCommonJsTask ??= jsRuntime.InvokeAsync<IJSObjectReference>("import", JSInteropVersionHelper.CommonJsUrl).AsTask();
var commonModule = await _loadCommonJsTask.ConfigureAwait(true);
if (commonModule != null)
{
return await commonModule.InvokeAsync<bool>("confirm", "Changes have been made, continue and lose those changes?").ConfigureAwait(true);
}
}
return true;
}
MagicSuite currently works around this with a prerender-safe INavigationCancelService registered after AddPanoramicDataBlazor() (panoramicdata/MagicSuite#102); that override should be removed once a fixed version is published.
PR to follow.
🤖 Generated with Claude Code
Symptom
Any Blazor Server app with prerendering enabled that registers the library's services (
AddPanoramicDataBlazor()orAddNavigationCancelService()) logs this on every page load:Observed in MagicSuite's Ops Dashboard (MS-25810) with PanoramicData.Blazor 10.0.205; attributed with a
FirstChanceExceptionstack capture, which points directly at theNavigationCancelServiceconstructor.Cause
Services/NavigationCancelService.csstarts a JS interop call in a field initialiser:The service is scoped and injected into
PDNavLink/PDForm, so during the static prerender pass a fresh instance is constructed per request.RemoteJSRuntimeis not yet attached to a circuit, the task faults immediately, and nothing observes it unlessProceedAsynchappens to run — so the finalizer reports it as anUnobservedTaskException, once per page load. Because the fault arrives via the finalizer, the stack never identifies the component, which makes it expensive for consumers to attribute.Suggested fix
Load the module lazily on first use instead of at construction — by the time a listener cancels a navigation the circuit is interactive:
MagicSuite currently works around this with a prerender-safe
INavigationCancelServiceregistered afterAddPanoramicDataBlazor()(panoramicdata/MagicSuite#102); that override should be removed once a fixed version is published.PR to follow.
🤖 Generated with Claude Code