{"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":null,"httpStatus":null,"severity":"error","filePath":"src/Components/Web.JS/src/Services/WebRootComponentManager.ts","lineNumber":479,"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":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Components/Web.JS/src/Services/WebRootComponentManager.ts#L461-L497","documentation":"Thrown by WebRootComponentManager.resolveRootComponent(ssrComponentId) at line 479 when the given SSR component ID is not present in the _rootComponentsBySsrComponentId map. Each interactive root component is assigned a numeric SSR id during discovery; .NET calls back into this resolver when it needs the descriptor for a known root. A miss means .NET is asking about a component the JS side never registered (or already removed).","triggerScenarios":"The .NET runtime invokes resolveRootComponent with an ssrComponentId that was never added, or that was already disposed/removed via onAfterUpdateRootComponents('remove'). Also possible if the DOM markers were stripped before resolution.","commonSituations":"SSR output and interactive runtime out of sync (stale prerendered page served from cache while a fresh circuit starts). DOM manipulation or aggressive content replacement that removes blazor component markers before the circuit references them. Concurrent navigation that disposes a root component while a queued update still references it. Version mismatch between prerendered HTML and runtime.","solutions":["Ensure the prerendered HTML and the interactive runtime come from the same app version — disable aggressive output caching that serves stale SSR markup.","Avoid third-party scripts or DOM-diffing libraries that strip or relocate the blazor component markers.","Reproduce with enhanced navigation/logging enabled to see if a navigation removed the component mid-update.","Upgrade the runtime; resolution races have been addressed across Blazor releases."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function hasRootComponent(manager: WebRootComponentManager, id: number): boolean {\n  // reflect on internal map via a guarded resolve\n  try { manager.resolveRootComponent(id); return true; } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const descriptor = manager.resolveRootComponent(ssrId);\n} catch (e) {\n  console.warn('Root component not resolvable, skipping update', ssrId, e);\n}","preventionTips":["Do not serve stale prerendered HTML from cache alongside a fresh circuit.","Prevent third-party scripts from removing blazor component markers.","Avoid concurrent navigations that dispose roots mid-update.","Keep prerender markup and runtime on the same version."],"tags":["blazor","root-components","ssr","circuit","dom"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}