dotnet/aspnetcore · error · Error

Could not resolve a root component with SSR component ID

Error message

Could not resolve a root component with SSR component ID '${ssrComponentId}'.

What it means

WebRootComponentManager maps SSR component IDs (the integer assigned during static SSR) to ComponentDescriptors. resolveRootComponent looks one up; if the SSR component ID was never registered (or already removed), the map has no entry and the lookup fails. This typically happens when interactive rendering tries to attach to an SSR component that is not present in the current document.

Solutions

  1. Avoid manipulating or removing Blazor component markers from the DOM via custom JS.
  2. Ensure the same SSR-rendered document is used for interactive attach (no caching of partial fragments).
  3. Align render modes so SSR and interactive sides agree on component IDs.
  4. Reload the page to regenerate SSR markers and retry the interactive attach.

Example fix

// before: custom JS removes a component's wrapper element before attach
wrapper.remove();
// after: let Blazor manage the lifecycle; only remove via framework APIs
// (do not strip markers the framework needs)
Defensive patterns

Strategy: validation

Validate before calling

// Before resolving, check the manager has the SSR component registered
// (Use the framework's lifecycle; if you wrap it, validate existence first)
function rootComponentExists(manager: any, id: number): boolean {
  // heuristic: rely on the framework; do not poke private maps
  return id >= 0;
}

Try / catch

try {
  manager.resolveRootComponent(ssrId);
} catch (e) {
  if (/Could not resolve a root component/i.test((e as Error).message)) {
    // SSR markers may be stale; reload to regenerate them
    window.location.reload();
  } else throw e;
}

Prevention

When it happens

Trigger: Thrown at line 483 when _rootComponentsBySsrComponentId.get(ssrComponentId) returns undefined inside resolveRootComponent. Called when an interactive runtime asks for the descriptor for an SSR ID it believes exists.

Common situations: DOM mutation removed the component's SSR marker before interactive attach; mismatched render modes where the interactive side references an SSR ID from a different document; a cached/stale SSR document served without the expected component; manual DOM manipulation removing Blazor markers.

Related errors


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

Appendix: source

Thrown at src/Components/Web.JS/src/Services/WebRootComponentManager.ts:483

        // After the renderer attaches, we'll handle the removal of this descriptor again.
        return null;
      }

      // Since the component will be getting completedly diposed from .NET (rather than replaced by another component, which can
      // happen as a result of an 'update' operation), we indicate that its content should no longer be preserved on disposal.
      setShouldPreserveContentOnInteractiveComponentDisposal(component.descriptor.start as unknown as LogicalElement, false);

      // This component was removed from the document and we've assigned a renderer ID,
      // so we'll dispose it in .NET.
      component.hasPendingRemoveOperation = true;
      return { type: 'remove', ssrComponentId: component.ssrComponentId };
    }
  }

  public resolveRootComponent(ssrComponentId: number): ComponentDescriptor {
    const component = this._rootComponentsBySsrComponentId.get(ssrComponentId);
    if (!component) {
      throw new Error(`Could not resolve a root component with SSR component ID '${ssrComponentId}'.`);
    }

    return component.descriptor;
  }

  public onAfterUpdateRootComponents(batchId: number): void {
    const batch = this._pendingOperationBatches[batchId];
    delete this._pendingOperationBatches[batchId];

    for (const operation of batch.operations) {
      switch (operation.type) {
        case 'remove': {
          // We can stop tracking this component now that .NET has acknowledged its removal.
          const component = this._rootComponentsBySsrComponentId.get(operation.ssrComponentId);
          if (component) {
            this.unregisterComponent(component);
          }
          break;

View on GitHub (pinned to 3600ca084e)