dotnet runtime_exception ai_generated true

InvalidOperationException: JavaScript interop calls cannot be issued during server-side prerendering.

ID: dotnet/blazor-prerender-state

Also available as: JSON · Markdown
90%Fix Rate
91%Confidence
80Evidence
2023-01-01First Seen

Version Compatibility

VersionStatusIntroducedDeprecatedNotes
8 active

Root Cause

Blazor Server and Blazor United (SSR) prerender components on the server where there is no browser or JavaScript runtime. Calling IJSRuntime methods (InvokeAsync, InvokeVoidAsync) during OnInitialized/OnInitializedAsync or OnParametersSet during prerendering fails because there is no SignalR circuit or browser connection yet. Fix by deferring JS interop to OnAfterRender/OnAfterRenderAsync, checking the render mode, or disabling prerendering for the component.

generic

Workarounds

  1. 92% success Move JS interop calls to OnAfterRenderAsync with a firstRender guard
    OnAfterRenderAsync is only called after the component has rendered in the browser, guaranteeing a JS runtime is available: protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var value = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "key"); myState = value; StateHasChanged(); } } This is the idiomatic Blazor pattern for JS interop initialization
  2. 88% success Disable prerendering for the specific component that requires JS interop
    Set the render mode to disable prerendering for components that fundamentally depend on JS interop at initialization. In .NET 8 Blazor United: <MyComponent @rendermode="new InteractiveServerRenderMode(prerender: false)" /> or <MyComponent @rendermode="new InteractiveWebAssemblyRenderMode(prerender: false)" />. This trades SSR benefits for the component in exchange for correct JS interop behavior. Use sparingly and only for components where JS is essential at init
  3. 85% success Use PersistentComponentState to preserve state across prerender and interactive phases
    Inject PersistentComponentState to transfer server-side data to the interactive phase without needing JS during prerender: [Inject] PersistentComponentState ApplicationState { get; set; } In OnInitializedAsync, register state for persistence during prerender: ApplicationState.RegisterOnPersisting(() => { ApplicationState.PersistAsJson("myData", data); return Task.CompletedTask; }); On interactive render: ApplicationState.TryTakeFromJson<MyData>("myData", out var restored); This avoids JS interop entirely during prerender while preserving data

Dead Ends

Common approaches that don't work:

  1. Wrapping the JS interop call in a try-catch to silently ignore the prerender failure 80% fail

    The component renders without the JS-dependent state, causing visual flicker, missing data, or broken UI on initial load. The catch block masks a real architectural issue and the component's first render will always be incomplete, creating a poor user experience and potential hydration mismatches

  2. Injecting NavigationManager to detect prerendering and skip all initialization logic 75% fail

    NavigationManager is available during prerendering and does not reliably indicate the render phase. Skipping all initialization during prerender means the server-rendered HTML has no meaningful content, defeating the purpose of SSR for SEO and first-paint performance. The component renders as empty on the server

  3. Moving the JS interop call to the component constructor or field initializer 95% fail

    The constructor runs even earlier than OnInitialized and the DI-injected IJSRuntime is not yet available or is in the same prerender context. This typically results in a NullReferenceException or the same InvalidOperationException, now thrown from the constructor which is harder to debug

Error Chain

Leads to:
Preceded by:
Frequently confused with: