dotnet/aspnetcore · error · Error

Descriptors must be assigned a renderer ID before getting…

Error message

Descriptors must be assigned a renderer ID before getting used as root components

What it means

WebRootComponentManager computes add/update/remove operations per root component and groups them by renderer. Each component used as a root must already have an assignedRendererId (set during earlier discovery/assignment). The throw fires when an operation was produced for a component but its assignedRendererId is falsy — a violated internal invariant indicating assignment logic ran out of order.

Solutions

  1. Do not bypass Blazor's root-component registration APIs; use the official addRootComponent / registerForAuthentication flows.
  2. Ensure the renderer is started and has assigned an ID before components are added as roots.
  3. Align Blazor JS bundle and .NET runtime versions.
  4. If using custom JS interop to register roots, await the renderer's ready signal first.

Example fix

// before: registering a root component before the renderer is ready
Blazor.rootComponents.add(...);
// after: wait for the runtime, then register
await Blazor.start();
// renderer assigns IDs as part of its init; register after start resolves
Blazor.rootComponents.add(...);
Defensive patterns

Strategy: validation

Validate before calling

// Before registering a root component, ensure the renderer has assigned an ID
const comp = rootComponentManagerInstance; // your WebRootComponentManager
// Use the public API which performs assignment; do not bypass it
// (no manual pre-check is part of the public surface — rely on framework ordering)

Type guard

function hasAssignedRendererId(c: any): boolean {
  return c && c.assignedRendererId !== undefined && c.assignedRendererId !== null;
}

Try / catch

try {
  // root component op
} catch (e) {
  if (/assigned a renderer ID/i.test((e as Error).message)) {
    await (window as any).Blazor.start();
    // retry registration after the runtime is ready
  } else throw e;
}

Prevention

When it happens

Trigger: Thrown at line 292 when an operation (add/update/remove) was generated for a component whose assignedRendererId is undefined/falsy. Happens inside the operations-discovery loop after operationsByRendererId grouping begins.

Common situations: Custom root-component registration interop that bypasses the normal renderer-ID assignment; a race where a component is added to root state before its renderer is chosen; version skew between the root-component-management JS and the .NET host; mixing manual JS root component APIs with declarative ones.

Related errors


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

Appendix: source

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

    for (const component of components) {
      const operation = this.determinePendingOperation(component);

      // Capture the renderer IDs for the available components to determine the
      // effective render modes in the document for discovering new persisted state.
      // This must happen after determining the pending operation, because a component
      // that is being activated for the first time only gets assigned a renderer ID
      // as part of computing its 'add' operation.
      if (discoverNewState && component.assignedRendererId !== undefined) {
        rendererIds.add(component.assignedRendererId);
      }

      if (!operation) {
        continue;
      }

      const rendererId = component.assignedRendererId;
      if (!rendererId) {
        throw new Error('Descriptors must be assigned a renderer ID before getting used as root components');
      }

      let operations = operationsByRendererId.get(rendererId);
      if (!operations) {
        operations = [];
        operationsByRendererId.set(rendererId, operations);
      }

      operations.push(operation);
    }

    let serverState = '';
    let webAssemblyState = '';
    if (discoverNewState) {
      for (const rendererId of rendererIds) {
        if (rendererId === WebRendererId.Server) {
          // We have server components. Try to discover the persisted state for them
          // and if there are no updates on this batch, push an empty set of operations

View on GitHub (pinned to 3600ca084e)