dotnet/aspnetcore · error · Error

Blazor has already started.

Error message

Blazor has already started.

What it means

Thrown by boot() in Boot.WebAssembly.ts when the module-level 'started' flag is already true. This is the top-level entrypoint guard for the standalone Blazor WebAssembly boot path; re-entry would re-discover components, re-set options, and re-run startWebAssembly.

Solutions

  1. Call Blazor.start() at most once per page; remove duplicate calls.
  2. Disable autostart and start manually from a single location when you need control.
  3. Memoize the boot promise so retries reuse the first invocation.
  4. Audit bundler/HMR and the host HTML for double-inclusion of the boot script.

Example fix

// before
function safeRestart() { Blazor.start(); } // each call throws

// after
let p;
function safeRestart() { return p ?? (p = Blazor.start()); }
Defensive patterns

Strategy: validation

Validate before calling

// Guard WebAssembly boot at the call site.
let wasmStarted;
function startWasmOnce(opts) {
  if (wasmStarted) return wasmStarted;
  wasmStarted = Blazor.start(opts);
  return wasmStarted;
}

Try / catch

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

Prevention

When it happens

Trigger: Calling Blazor.start() in a WebAssembly app more than once per page, or having the boot module evaluated twice.

Common situations: Autostart plus a manual Blazor.start(); a retry/recovery routine that re-invokes boot after a partially successful start; HMR or bundler duplication evaluating Boot.WebAssembly.ts twice; a host page that includes blazor.webassembly.js more than once.

Related errors


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

Appendix: source

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

/* eslint-disable array-element-newline */
import { Blazor } from './GlobalExports';
import { shouldAutoStart } from './BootCommon';
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
import { setWebAssemblyOptions, startWebAssembly } from './Boot.WebAssembly.Common';
import { WebAssemblyComponentDescriptor, discoverComponents, discoverWebAssemblyOptions } from './Services/ComponentDescriptorDiscovery';
import { DotNet } from '@microsoft/dotnet-js-interop';
import { InitialRootComponentsList } from './Services/InitialRootComponentsList';
import { JSEventRegistry } from './Services/JSEventRegistry';
import { printErr } from './Platform/Mono/MonoPlatform';

type BlazorWebAssemblyStartOptions = Partial<WebAssemblyStartOptions> & { webAssembly?: Partial<WebAssemblyStartOptions> };

let started = false;

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

  // Accept the `webAssembly` property from the blazor.web.js options format
  const normalizedOptions = options?.webAssembly ?? options ?? {};
  setWebAssemblyOptions(Promise.resolve(normalizedOptions));

  JSEventRegistry.create(Blazor);
  const webAssemblyComponents = discoverComponents(document, 'webassembly') as WebAssemblyComponentDescriptor[];
  const webAssemblyOptions = discoverWebAssemblyOptions(document);

  const components = new InitialRootComponentsList(webAssemblyComponents);
  await startWebAssembly(components, webAssemblyOptions);
}

Blazor.start = boot;
window['DotNet'] = DotNet;

View on GitHub (pinned to 3600ca084e)