{"record":{"id":"49e7e409e3ee353c","repo":"dotnet/aspnetcore","slug":"could-not-resolve-a-root-component-with-ssr-compon","errorCode":null,"errorMessage":"Could not resolve a root component with SSR component ID '${ssrComponentId}'.","messagePattern":"Could not resolve a root component with SSR component ID '(.+?)'\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/Components/Web.JS/src/Services/WebRootComponentManager.ts","lineNumber":483,"sourceCode":"        // After the renderer attaches, we'll handle the removal of this descriptor again.\n        return null;\n      }\n\n      // Since the component will be getting completedly diposed from .NET (rather than replaced by another component, which can\n      // happen as a result of an 'update' operation), we indicate that its content should no longer be preserved on disposal.\n      setShouldPreserveContentOnInteractiveComponentDisposal(component.descriptor.start as unknown as LogicalElement, false);\n\n      // This component was removed from the document and we've assigned a renderer ID,\n      // so we'll dispose it in .NET.\n      component.hasPendingRemoveOperation = true;\n      return { type: 'remove', ssrComponentId: component.ssrComponentId };\n    }\n  }\n\n  public resolveRootComponent(ssrComponentId: number): ComponentDescriptor {\n    const component = this._rootComponentsBySsrComponentId.get(ssrComponentId);\n    if (!component) {\n      throw new Error(`Could not resolve a root component with SSR component ID '${ssrComponentId}'.`);\n    }\n\n    return component.descriptor;\n  }\n\n  public onAfterUpdateRootComponents(batchId: number): void {\n    const batch = this._pendingOperationBatches[batchId];\n    delete this._pendingOperationBatches[batchId];\n\n    for (const operation of batch.operations) {\n      switch (operation.type) {\n        case 'remove': {\n          // We can stop tracking this component now that .NET has acknowledged its removal.\n          const component = this._rootComponentsBySsrComponentId.get(operation.ssrComponentId);\n          if (component) {\n            this.unregisterComponent(component);\n          }\n          break;","sourceCodeStart":465,"sourceCodeEnd":501,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/Components/Web.JS/src/Services/WebRootComponentManager.ts#L465-L501","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Avoid manipulating or removing Blazor component markers from the DOM via custom JS.","Ensure the same SSR-rendered document is used for interactive attach (no caching of partial fragments).","Align render modes so SSR and interactive sides agree on component IDs.","Reload the page to regenerate SSR markers and retry the interactive attach."],"exampleFix":"// before: custom JS removes a component's wrapper element before attach\nwrapper.remove();\n// after: let Blazor manage the lifecycle; only remove via framework APIs\n// (do not strip markers the framework needs)","handlingStrategy":"validation","validationCode":"// Before resolving, check the manager has the SSR component registered\n// (Use the framework's lifecycle; if you wrap it, validate existence first)\nfunction rootComponentExists(manager: any, id: number): boolean {\n  // heuristic: rely on the framework; do not poke private maps\n  return id >= 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n  manager.resolveRootComponent(ssrId);\n} catch (e) {\n  if (/Could not resolve a root component/i.test((e as Error).message)) {\n    // SSR markers may be stale; reload to regenerate them\n    window.location.reload();\n  } else throw e;\n}","preventionTips":["Do not remove or mutate Blazor component markers via custom JS.","Avoid serving cached/partial SSR fragments for interactive pages.","Keep render modes consistent across SSR and interactive attach.","Reload to regenerate SSR markers when in doubt."],"tags":["blazor","root-components","ssr","component-id","dom-mutation"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}