dotnet/aspnetcore · critical

Dynamic root components have already been enabled.

Error message

Dynamic root components have already been enabled.

What it means

Thrown by enableJSRootComponents when a manager is already set and the new rendererId differs from currentRendererId. Blazor allows the same renderer type to re-enable (e.g. circuit restart) by replacing the manager, but a different renderer type (Server vs WebAssembly) trying to enable dynamic root components in one page is a multi-host scenario that is unsupported. The rendererId mismatch is the signal for a different host.

Solutions

  1. Use only one Blazor hosting model (Server OR WebAssembly) per page.
  2. For micro-frontends, isolate each Blazor app in its own iframe or separate JS context.
  3. If you genuinely need both, run them on separate pages/routes.
  4. Check that blazor.boot.json and the script tag (blazor.server.js vs blazor.webassembly.js) match the intended single host.

Example fix

// before: both loaded
<script src="_framework/blazor.server.js"></script>
<script src="_framework/blazor.webassembly.js"></script>

// after: pick one
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
Defensive patterns

Strategy: validation

Validate before calling

// Before starting, assert only one hosting model is present.
const scripts = document.querySelectorAll('script[src*="blazor."]');
if (scripts.length > 1) {
  throw new Error('Multiple Blazor hosting scripts detected on one page.');
}

Try / catch

try {
  await Blazor.start();
} catch (e) {
  if (/already been enabled/.test(String(e?.message))) {
    // already started by another host — surface a clear config error to the team
  }
}

Prevention

When it happens

Trigger: Loading both Blazor Server and Blazor WebAssembly on the same page and both calling enableJSRootComponents; a hybrid hosting setup; a misconfigured boot config that starts two renderer types.

Common situations: Migrating an app from Server to WebAssembly and accidentally leaving both script tags; a portal/micro-frontend that embeds two Blazor apps sharing the global Blazor object.

Related errors


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

Appendix: source

Thrown at src/Components/Web.JS/src/Rendering/JSRootComponents.ts:129

      for (const jsEventCallbackWrapper of this._jsEventCallbackWrappers.values()) {
        jsEventCallbackWrapper.dispose();
      }
    }
  }
}

// Called by the framework
export function enableJSRootComponents(
  rendererId: number,
  managerInstance: DotNet.DotNetObject,
  jsComponentParameters: JSComponentParametersByIdentifier,
  jsComponentInitializers: JSComponentIdentifiersByInitializer
): void {
  if (manager && currentRendererId !== rendererId) {
    // A different renderer type (e.g., Server vs WebAssembly) is trying to enable JS root components.
    // This is a multi-host scenario which is not supported for dynamic root components.
    throw new Error('Dynamic root components have already been enabled.');
  }

  // When the same renderer type re-enables (e.g., circuit restart or new circuit on same page),
  // accept the new manager. The old manager's DotNetObjectReference is no longer valid anyway
  // because the old circuit is gone. We don't dispose the old manager - doing so would cause
  // JSDisconnectedException because the circuit that created it no longer exists.
  currentRendererId = rendererId;
  manager = managerInstance;
  jsComponentParametersByIdentifier = jsComponentParameters;

  if (!hasInitializedJsComponents) {
    // Call the registered initializers. This is an arbitrary subset of the JS component types that are registered
    // on the .NET side - just those of them that require some JS-side initialization (e.g., to register them
    // as custom elements).
    for (const [initializerIdentifier, componentIdentifiers] of Object.entries(jsComponentInitializers)) {
      const initializerFunc = DotNet.findJSFunction(initializerIdentifier, 0) as JSComponentInitializerCallback;
      for (const componentIdentifier of componentIdentifiers) {
        const parameters = jsComponentParameters[componentIdentifier];

View on GitHub (pinned to 3600ca084e)