dotnet/aspnetcore · error · Error

Blazor has already started.

Error message

Blazor has already started.

What it means

Thrown by boot() in Boot.WebView.ts (the .NET MAUI / WebView Blazor entry) when its module-level 'started' flag is already true. The WebView boot attaches the IPC dispatcher, starts the IPC receiver, and wires navigation — all single-shot operations for the lifetime of the WebView.

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 294cab2f9b)

Solutions

  1. Let the BlazorWebView start Blazor itself; do not call Blazor.start() manually unless you have disabled autostart.
  2. Ensure the WebView performs a full navigation (fresh JS context) rather than re-evaluating scripts in place.
  3. Guard the call with a window-level flag before invoking.

Example fix

// before
<script src="_framework/blazor.webview.js" autostart></script>
<script>Blazor.start();</script> <!-- throws -->

// after: rely on autostart, or
<script src="_framework/blazor.webview.js"></script>
<script>Blazor.start();</script>
Defensive patterns

Strategy: validation

Validate before calling

function startWebViewBlazorOnce() {
  if ((window as any).__blazorWebViewStarted) return Promise.resolve();
  (window as any).__blazorWebViewStarted = true;
  return Blazor.start();
}

Type guard

function webViewBlazorStarted(): boolean {
  return !!(window as any).__blazorWebViewStarted;
}

Try / catch

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

Prevention

When it happens

Trigger: Calling Blazor.start() a second time inside a BlazorWebView, or the WebView reloading and re-evaluating the boot bundle while a prior boot's flag persists in the same JS context (e.g. via a non-isolated script scope).

Common situations: A native reload that does not reset the JS context; manually invoking Blazor.start() in addition to the auto-start the WebView performs; an embedded page that includes the script twice; testing harnesses that re-run boot without a fresh WebView.

Related errors


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