dotnet/aspnetcore · error · Error

Blazor has already started.

Error message

Blazor has already started.

What it means

Thrown by boot() in Boot.WebView.ts when the module-level 'started' flag is already true. This guards the Blazor WebView boot path (used inside native shells via IPC), which must attach the DotNet dispatcher, start the IPC receiver, and run initializers exactly once per WebView instance.

Solutions

  1. Initialize Blazor in the WebView exactly once; reload the WebView (full navigation) if you truly need a fresh Blazor instance.
  2. Guard any native-initiated boot calls with a flag set on first completion.
  3. Avoid calling Blazor.start() from multiple native lifecycle events.
  4. Inspect the WebView bootstrap script inclusion for duplication.

Example fix

// before — native handler re-boots on resume
void OnResume() { webView.CoreWebView2.ExecuteScriptAsync("Blazor.start()"); }

// after — only boot once per WebView navigation
bool booted;
void OnResume() {
  if (!booted) { webView.CoreWebView2.ExecuteScriptAsync("Blazor.start()"); booted = true; }
}
Defensive patterns

Strategy: validation

Validate before calling

// Boot Blazor in the WebView exactly once per navigation.
let webViewBooted;
function bootWebViewOnce() {
  if (webViewBooted) return;
  webViewBooted = true;
  Blazor.start();
}

Try / catch

try {
  await Blazor.start();
} catch (e) {
  if (/already started/.test(e.message)) return;
  throw e;
}

Prevention

When it happens

Trigger: Calling the WebView boot path twice in the same WebView, or framework re-entry (e.g. native host triggering re-initialization while a boot is in flight or completed).

Common situations: Native shell code that re-initializes the WebView without reloading it; misconfigured WebView lifecycle hooks re-running boot on resume; HMR-style reload inside the WebView; bundler duplication evaluating Boot.WebView.ts twice.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/e345705e58a80dc5. Report an issue: GitHub.

Appendix: source

Thrown at src/Components/Web.JS/src/Boot.WebView.ts:20

// The .NET Foundation licenses this file to you under the MIT license.

import { DotNet } from '@microsoft/dotnet-js-interop';
import { Blazor } from './GlobalExports';
import { shouldAutoStart } from './BootCommon';
import { internalFunctions as navigationManagerFunctions } from './Services/NavigationManager';
import { startIpcReceiver } from './Platform/WebView/WebViewIpcReceiver';
import { sendAttachPage, sendBeginInvokeDotNetFromJS, sendEndInvokeJSFromDotNet, sendByteArray, sendLocationChanged, sendLocationChanging } from './Platform/WebView/WebViewIpcSender';
import { fetchAndInvokeInitializers } from './JSInitializers/JSInitializers.WebView';
import { receiveDotNetDataStream } from './StreamingInterop';
import { WebRendererId } from './Rendering/WebRendererId';

let started = false;

export let dispatcher: DotNet.ICallDispatcher;

async function boot(): Promise<void> {
  if (started) {
    throw new Error('Blazor has already started.');
  }
  started = true;

  dispatcher = DotNet.attachDispatcher({
    beginInvokeDotNetFromJS: sendBeginInvokeDotNetFromJS,
    endInvokeJSFromDotNet: sendEndInvokeJSFromDotNet,
    sendByteArray: sendByteArray,
  });

  const jsInitializer = await fetchAndInvokeInitializers();

  startIpcReceiver();

  Blazor._internal.receiveWebViewDotNetDataStream = receiveWebViewDotNetDataStream;

  navigationManagerFunctions.enableNavigationInterception(WebRendererId.WebView);
  navigationManagerFunctions.listenForNavigationEvents(WebRendererId.WebView, sendLocationChanged, sendLocationChanging);

View on GitHub (pinned to 3600ca084e)