dotnet/aspnetcore · critical · InvalidOperationException
Render output is invalid for component of type '{component.G
Error message
Render output is invalid for component of type '{component.GetType().FullName}'. A frame of type '{invalidFrame.FrameType}' was left unclosed. Do not use try/catch inside rendering logic, because partial output cannot be undone. What it means
Thrown by RenderTreeBuilder.AssertTreeIsValid (called after a component render). If _openElementIndices still has entries, some OpenElement/OpenComponent/OpenRegion was never matched by its Close*. An unclosed frame would corrupt diffing, so the entire render is rejected. The message explicitly forbids try/catch in render logic because partial output cannot be rolled back.
Source
Thrown at src/Components/Components/src/Rendering/RenderTreeBuilder.cs:817
ref var frame = ref frames[frameIndex];
if (frame.FrameTypeField != RenderTreeFrameType.Attribute)
{
throw new InvalidOperationException(
$"The frame at index {frameIndex} is of type '{frame.FrameTypeField}', not '{RenderTreeFrameType.Attribute}'.");
}
frame.AttributeValueField = value;
}
internal void AssertTreeIsValid(IComponent component)
{
if (_openElementIndices.Count > 0)
{
// It's never valid to leave an element/component/region unclosed. Doing so
// could cause undefined behavior in diffing.
ref var invalidFrame = ref _entries.Buffer[_openElementIndices.Peek()];
throw new InvalidOperationException($"Render output is invalid for component of type '{component.GetType().FullName}'. A frame of type '{invalidFrame.FrameType}' was left unclosed. Do not use try/catch inside rendering logic, because partial output cannot be undone.");
}
}
// Internal for testing
internal void ProcessDuplicateAttributes(int first)
{
Debug.Assert(_hasSeenAddMultipleAttributes);
// When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
// the element/component. However, we also don't know the end index we should look at because it
// will contain nested content.
var buffer = _entries.Buffer;
var last = _entries.Count - 1;
for (var i = first; i <= last; i++)
{
if (buffer[i].FrameTypeField != RenderTreeFrameType.Attribute)
{View on GitHub (pinned to 294cab2f9b)
Solutions
- Make every Open* strictly paired with a Close* in the same control-flow path; prefer try/finally around the Close if needed for cleanup (though the guidance is to never throw in render logic).
- Remove all try/catch from RenderFragment bodies — fix the underlying throw instead so partial output never occurs.
- Audit conditionals/early-returns/loops that contain Open* without their matching Close*.
- If using regions, ensure each OpenRegion has a CloseRegion even on all branches.
Example fix
// before
builder.OpenElement(0, "div");
if (show) { builder.AddContent(1, "x"); return; } // CloseElement skipped
builder.CloseElement();
// after
builder.OpenElement(0, "div");
if (show) { builder.AddContent(1, "x"); }
builder.CloseElement(); Defensive patterns
Strategy: validation
Validate before calling
// Do NOT try/catch in render logic. Instead guarantee balanced Open/Close structurally.
// A debug assertion helper you can call inside a RenderFragment:
#if DEBUG
void AssertBalanced(RenderTreeBuilder b) { /* mirror your opens/closes in a debug counter */ }
#endif
// The real fix is structural: every Open* has a matching Close* on all paths. Prevention
- Never wrap render-fragment bodies in try/catch — the framework cannot roll back partial output; fix the root cause instead.
- Ensure every OpenElement/OpenComponent/OpenRegion has a matching Close on every code path (including early returns and exceptions).
- Avoid early returns between an Open and its Close; close before returning.
- In generated/templated trees, unit-test for balance by counting open vs close calls.
When it happens
Trigger: Any render fragment where an Open* call lacks a matching Close* — a missing CloseElement/CloseComponent/CloseRegion, an early return inside the fragment, or an exception thrown between Open and Close that unwinds the stack. The throw fires on the next render attempt.
Common situations: A conditional `if (cond) builder.OpenElement(...);` whose CloseElement is outside the if; a try/catch inside a RenderFragment that swallows an error after an Open; an exception in nested rendering that leaves the builder half-populated; miscounted open/close after a refactor.
Related errors
- Element reference captures may only be added as children of
- Component reference captures may only be added as children o
- There is no enclosing component frame.
- The enclosing frame is not of the required type 'Component'.
- Named events may only be added as children of frames of type
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/b3264d315089cc47.
Report an issue: GitHub.