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
- Do not bypass Blazor's root-component registration APIs; use the official addRootComponent / registerForAuthentication flows.
- Ensure the renderer is started and has assigned an ID before components are added as roots.
- Align Blazor JS bundle and .NET runtime versions.
- 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
- Use Blazor's official root-component registration APIs only.
- Await Blazor.start before registering roots.
- Avoid mixing manual JS root APIs with declarative ones.
- Keep Blazor JS and .NET runtime versions aligned.
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
- Unexpected renderer ID
- Blazor WebAssembly has not initialized.
- Cannot merge mismatching component descriptors
- Could not resolve a root component with SSR component ID
- Dynamic root components have already been enabled.
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 operationsView on GitHub (pinned to 3600ca084e)