dotnet/aspnetcore · error · Error
Could not resolve a root component with SSR component ID
Error message
Could not resolve a root component with SSR component ID '${ssrComponentId}'. What it means
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.
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.
Example fix
// before: custom JS removes a component's wrapper element before attach wrapper.remove(); // after: let Blazor manage the lifecycle; only remove via framework APIs // (do not strip markers the framework needs)
Defensive patterns
Strategy: validation
Validate before calling
// Before resolving, check the manager has the SSR component registered
// (Use the framework's lifecycle; if you wrap it, validate existence first)
function rootComponentExists(manager: any, id: number): boolean {
// heuristic: rely on the framework; do not poke private maps
return id >= 0;
} Try / catch
try {
manager.resolveRootComponent(ssrId);
} catch (e) {
if (/Could not resolve a root component/i.test((e as Error).message)) {
// SSR markers may be stale; reload to regenerate them
window.location.reload();
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot merge mismatching component descriptors
- Could not find an end component comment for
- Descriptors must be assigned a renderer ID before getting…
- Dynamic root components have already been enabled.
- Dynamic root components have not been enabled in this…
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/49e7e409e3ee353c.
Report an issue: GitHub.
Appendix: source
Thrown at src/Components/Web.JS/src/Services/WebRootComponentManager.ts:483
// After the renderer attaches, we'll handle the removal of this descriptor again.
return null;
}
// Since the component will be getting completedly diposed from .NET (rather than replaced by another component, which can
// happen as a result of an 'update' operation), we indicate that its content should no longer be preserved on disposal.
setShouldPreserveContentOnInteractiveComponentDisposal(component.descriptor.start as unknown as LogicalElement, false);
// This component was removed from the document and we've assigned a renderer ID,
// so we'll dispose it in .NET.
component.hasPendingRemoveOperation = true;
return { type: 'remove', ssrComponentId: component.ssrComponentId };
}
}
public resolveRootComponent(ssrComponentId: number): ComponentDescriptor {
const component = this._rootComponentsBySsrComponentId.get(ssrComponentId);
if (!component) {
throw new Error(`Could not resolve a root component with SSR component ID '${ssrComponentId}'.`);
}
return component.descriptor;
}
public onAfterUpdateRootComponents(batchId: number): void {
const batch = this._pendingOperationBatches[batchId];
delete this._pendingOperationBatches[batchId];
for (const operation of batch.operations) {
switch (operation.type) {
case 'remove': {
// We can stop tracking this component now that .NET has acknowledged its removal.
const component = this._rootComponentsBySsrComponentId.get(operation.ssrComponentId);
if (component) {
this.unregisterComponent(component);
}
break;View on GitHub (pinned to 3600ca084e)