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
- Call startServer (or Blazor.start in Server mode) exactly once per page.
- Guard the call site with a module-level flag and a memoized promise so re-entry is a no-op.
- If running multiple Blazor Server apps, use a single boot and multiple root components rather than multiple boots.
- 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
- Boot Server exactly once per page.
- Disable autostart when starting manually.
- Centralize boot in a single module.
- Do not reuse the boot path across micro-frontends; use multiple root components instead.
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
- Blazor has already started.
- Circuit options have already been configured.
- Blazor has already started.
- Blazor has already started.
- Blazor has already started.
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)