dotnet/aspnetcore · error · Error

Blazor Server has already started.

Error message

Blazor Server has already started.

What it means

Thrown by startServer when the module-level serverStartPromise is already defined. startServer is the single entry point that constructs the SignalR circuit; calling it a second time indicates the Server platform was asked to boot twice.

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

Solutions

  1. Guard the call site with hasStartedServer() (Boot.Server.Common.ts:178) before starting.
  2. Disable autostart if you start manually (omit the autostart script tag or set the relevant config flag).
  3. Audit JS initializers and afterStarted callbacks to ensure none re-invoke start.
  4. Ensure the blazor.server.js bundle is loaded exactly once per page load.

Example fix

// before
Blazor.start();
// ... later ...
Blazor.start(); // throws: already started

// after
import { hasStartedServer } from './Boot.Server.Common';
if (!hasStartedServer()) {
  Blazor.start();
}
Defensive patterns

Strategy: validation

Validate before calling

import { hasStartedServer } from './Boot.Server.Common';

function startServerOnce(components: any, events: any) {
  if (hasStartedServer()) return Promise.resolve();
  return startServer(components, events);
}

Type guard

import { hasStartedServer } from './Boot.Server.Common';

function serverIsStarted(): boolean {
  return hasStartedServer();
}

Try / catch

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

Prevention

When it happens

Trigger: Invoking Blazor.start() twice on a Server-rendered app, or two boot paths both calling startServer (e.g. an initializer that re-boots, or a navigation handler that re-runs boot).

Common situations: Auto-start enabled plus a manual Blazor.start() call; HMR re-running the boot module; misrouted SPA navigation that re-mounts the Blazor script; mixing blazor.server.js with a wrapper that also boots.

Related errors


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