{"record":{"id":"d7d902d4d090f580","repo":"dotnet/aspnetcore","slug":"descriptors-must-be-assigned-a-renderer-id-before","errorCode":null,"errorMessage":"Descriptors must be assigned a renderer ID before getting used as root components","messagePattern":"Descriptors must be assigned a renderer ID before getting used as root components","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/Components/Web.JS/src/Services/WebRootComponentManager.ts","lineNumber":292,"sourceCode":"    for (const component of components) {\n      const operation = this.determinePendingOperation(component);\n\n      // Capture the renderer IDs for the available components to determine the\n      // effective render modes in the document for discovering new persisted state.\n      // This must happen after determining the pending operation, because a component\n      // that is being activated for the first time only gets assigned a renderer ID\n      // as part of computing its 'add' operation.\n      if (discoverNewState && component.assignedRendererId !== undefined) {\n        rendererIds.add(component.assignedRendererId);\n      }\n\n      if (!operation) {\n        continue;\n      }\n\n      const rendererId = component.assignedRendererId;\n      if (!rendererId) {\n        throw new Error('Descriptors must be assigned a renderer ID before getting used as root components');\n      }\n\n      let operations = operationsByRendererId.get(rendererId);\n      if (!operations) {\n        operations = [];\n        operationsByRendererId.set(rendererId, operations);\n      }\n\n      operations.push(operation);\n    }\n\n    let serverState = '';\n    let webAssemblyState = '';\n    if (discoverNewState) {\n      for (const rendererId of rendererIds) {\n        if (rendererId === WebRendererId.Server) {\n          // We have server components. Try to discover the persisted state for them\n          // and if there are no updates on this batch, push an empty set of operations","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/Components/Web.JS/src/Services/WebRootComponentManager.ts#L274-L310","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: registering a root component before the renderer is ready\nBlazor.rootComponents.add(...);\n// after: wait for the runtime, then register\nawait Blazor.start();\n// renderer assigns IDs as part of its init; register after start resolves\nBlazor.rootComponents.add(...);","handlingStrategy":"validation","validationCode":"// Before registering a root component, ensure the renderer has assigned an ID\nconst comp = rootComponentManagerInstance; // your WebRootComponentManager\n// Use the public API which performs assignment; do not bypass it\n// (no manual pre-check is part of the public surface — rely on framework ordering)","typeGuard":"function hasAssignedRendererId(c: any): boolean {\n  return c && c.assignedRendererId !== undefined && c.assignedRendererId !== null;\n}","tryCatchPattern":"try {\n  // root component op\n} catch (e) {\n  if (/assigned a renderer ID/i.test((e as Error).message)) {\n    await (window as any).Blazor.start();\n    // retry registration after the runtime is ready\n  } else throw e;\n}","preventionTips":["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."],"tags":["blazor","root-components","renderer-id","internal-invariant","initialization"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}