InvalidOperationException: JavaScript interop calls cannot be issued during server-side prerendering.
ID: dotnet/blazor-prerender-state
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| 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.
genericWorkarounds
-
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 -
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
-
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:
-
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
-
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
-
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