dotnet/aspnetcore · error · Error

Blazor Server has already started.

Error message

Blazor Server has already started.

What it means

Thrown by startServer in Boot.Server.Common when the module-level serverStartPromise is already defined. startServer is the single entrypoint for bringing up the SignalR circuit client; invoking it again indicates Blazor Server has been booted twice and the second invocation is rejected to preserve a single circuit lifecycle.

Solutions

  1. Call startServer (or Blazor.start in Server mode) exactly once per page.
  2. Guard the call site with a module-level flag and a memoized promise so re-entry is a no-op.
  3. If running multiple Blazor Server apps, use a single boot and multiple root components rather than multiple boots.
  4. Confirm autostart is disabled when you start manually.

Example fix

// before
async function ensureBlazor() {
  await Blazor.start(); // throws on second call
}

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

Strategy: validation

Validate before calling

// Memoize start so startServer is entered exactly once.
let serverPromise;
function startBlazorServerOnce() {
  return serverPromise ?? (serverPromise = Blazor.start());
}

Try / catch

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

Prevention

When it happens

Trigger: Calling the server boot path (directly or via Blazor.start for Server) twice in one page, or framework code re-entering startServer after a successful start.

Common situations: Manual Blazor.start() combined with autostart; hot-reload/HMR re-evaluating the boot module; a micro-frontend shell that mounts two Blazor Server apps via the same boot entrypoint; misrouted imports causing Boot.Server to load twice.

Related errors


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

Appendix: source

Thrown at src/Components/Web.JS/src/Boot.Server.Common.ts:40

let serverStartPromise: Promise<void>;
let circuitStarting: Promise<boolean> | undefined;

export function setCircuitOptions(initializersReady: Promise<Partial<CircuitStartOptions>>) {
  if (options) {
    throw new Error('Circuit options have already been configured.');
  }

  initializersPromise = setOptions(initializersReady);

  async function setOptions(initializers: Promise<Partial<CircuitStartOptions>>): Promise<void> {
    const configuredOptions = await initializers;
    options = resolveOptions(configuredOptions);
  }
}

export function startServer(components: RootComponentManager<ServerComponentDescriptor>, jsEventRegistry: JSEventRegistry): Promise<void> {
  if (serverStartPromise !== undefined) {
    throw new Error('Blazor Server has already started.');
  }

  serverStartPromise = new Promise(startServerCore.bind(null, components, jsEventRegistry));

  return serverStartPromise;
}

async function startServerCore(components: RootComponentManager<ServerComponentDescriptor>, jsEventRegistry: JSEventRegistry, resolve: () => void, _: any) {
  await initializersPromise;
  const jsInitializer = await fetchAndInvokeInitializers(options);

  appState = discoverServerPersistedState(document) || '';
  logger = new ConsoleLogger(options.logLevel);
  circuit = new CircuitManager(components, appState, options, logger, jsEventRegistry);

  addDispatchEventMiddleware((_browserRendererId, eventHandlerId, continuation) => {
    logger.log(LogLevel.Debug, `Dispatching event with handler id ${eventHandlerId}.`);
    continuation();

View on GitHub (pinned to 3600ca084e)