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
- Use only one Blazor hosting model (Server OR WebAssembly) per page.
- For micro-frontends, isolate each Blazor app in its own iframe or separate JS context.
- If you genuinely need both, run them on separate pages/routes.
- 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
- Load exactly one of blazor.server.js or blazor.webassembly.js per page.
- For micro-frontends, isolate Blazor apps in iframes.
- CI: assert the rendered HTML has only one Blazor boot script.
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
- ' ' is flagged with SingleDelivery, but the selected…
- Cannot merge mismatching component descriptors
- Could not load settings from
- Could not resolve a root component with SSR component ID
- Descriptors must be assigned a renderer ID before getting…
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)