dotnet/aspnetcore · error · Error

Blazor WebAssembly has already started.

Error message

Blazor WebAssembly has already started.

What it means

Thrown by startWebAssembly in Boot.WebAssembly.Common when the module-level startPromise is already defined. startWebAssembly runs the WebAssembly startup core (loading the Mono platform, root components, and entry point) and is intended to run exactly once; a second call would re-load the runtime and re-fire initializers.

Solutions

  1. Call Blazor.start() once; disable autostart if you start manually.
  2. Guard the call site with a memoized promise.
  3. Investigate bundler/HMR configuration if the boot module is being evaluated multiple times.
  4. Ensure the host page includes the boot script exactly once.

Example fix

// before
window.addEventListener('online', () => Blazor.start()); // fires after autostart

// after — single guarded entry
let startP;
function startOnce(){ return startP ?? (startP = Blazor.start()); }
Defensive patterns

Strategy: validation

Validate before calling

// Guard startWebAssembly entry so startPromise is created once.
let startPromise;
function ensureWasmStarted() {
  return startPromise ?? (startPromise = 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 WebAssembly boot path twice per page, either via two Blazor.start() invocations or via framework re-entry.

Common situations: Autostart plus manual start; an error-recovery routine that calls start again after a partial success; HMR re-evaluating Boot.WebAssembly.Common; duplicate script tags.

Related errors


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

Appendix: source

Thrown at src/Components/Web.JS/src/Boot.WebAssembly.Common.ts:75

}

export function setWebAssemblyOptions(initializersReady: Promise<Partial<WebAssemblyStartOptions>>) {
  if (options) {
    throw new Error('WebAssembly options have already been configured.');
  }
  setOptions(initializersReady);


  async function setOptions(initializers: Promise<Partial<WebAssemblyStartOptions>>) {
    const configuredOptions = await initializers;
    options = configuredOptions;
    resolveInitializersPromise();
  }
}

export function startWebAssembly(components: RootComponentManager<WebAssemblyComponentDescriptor>, options: WebAssemblyServerOptions | undefined): Promise<void> {
  if (startPromise !== undefined) {
    throw new Error('Blazor WebAssembly has already started.');
  }

  startPromise = new Promise(startCore.bind(null, components, options));

  return startPromise;
}

async function startCore(components: RootComponentManager<WebAssemblyComponentDescriptor>, options: WebAssemblyServerOptions | undefined, resolve, _) {
  if (inAuthRedirectIframe()) {
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    await new Promise(() => { }); // See inAuthRedirectIframe for explanation
  }

  const platformLoadPromise = loadWebAssemblyPlatformIfNotStarted(options);

  addDispatchEventMiddleware((browserRendererId, eventHandlerId, continuation) => {
    // It's extremely unusual, but an event can be raised while we're in the middle of synchronously applying a
    // renderbatch. For example, a renderbatch might mutate the DOM in such a way as to cause an <input> to lose

View on GitHub (pinned to 3600ca084e)