You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
My contribution adds a new instruction, prompt, agent, skill, workflow, or canvas extension file in the correct directory.
The file follows the required naming convention.
The content is clearly structured and follows the example format.
I have tested my instructions, prompt, agent, skill, workflow, or canvas extension with GitHub Copilot.
I have run npm start and verified that README.md is up to date.
I am targeting the main branch for this pull request.
Description
Adds instructions/blazor-rendering-performance.instructions.md (applyTo: '**/*.razor, **/*.razor.cs'). It turns the official ASP.NET Core Blazor rendering performance best practices into rules Copilot can apply while writing or reviewing Razor components:
When to optimize: hot paths only (components repeated at scale, high-frequency rerenders), so Copilot doesn't add ShouldRender, SetParametersAsync, or IHandleEvent to every component.
How change detection works: the exact list of known immutable parameter types from ChangeDetection.cs. Records, DateTimeOffset, TimeSpan, collections, and RenderFragment/ChildContent always trigger a child rerender.
Skipping subtree rerenders: immutable parameters, method-group EventCallbacks instead of capturing lambdas, and a ShouldRender change-key pattern that includes the stale-UI pitfall when the component also changes local state.
Virtualization: Items vs. ItemsProvider, ItemSize, SpacerElement, placeholders, keyboard focus, and the fact that Virtualize renders no items during static SSR or prerendering.
Lightweight components at scale: inlining vs. child components, RenderFragment templates, parameter count, IsFixed cascading values, attribute splatting, manual SetParametersAsync as a last resort (small gain on .NET 10+), and literal sequence numbers in RenderTreeBuilder code.
Events: throttling high-frequency DOM events through JS interop, non-rendering handlers (IHandleEvent, including the ErrorBoundary caveat), when StateHasChanged is and isn't needed, pre-created delegates in large loops, and @key.
A review checklist for components rendered at scale.
How it differs from existing resources
blazor.instructions.md covers general Blazor conventions, and its performance guidance is five generic bullets (for example "use ShouldRender() where appropriate"). This file is dedicated to rendering performance. It explains the mechanics that decide whether a rerender happens, the trade-offs between techniques, and when not to optimize.
Validation
Checked every technical claim against the Microsoft Learn articles (rendering performance, component rendering, virtualization, @key) and the ASP.NET Core reference source (ChangeDetection.cs, EventCallback.cs, EventCallbackFactory.cs, RenderTreeDiffBuilder.cs, Virtualize.cs).
Extracted every code sample verbatim from the instruction file into a .NET 10 Blazor Web App (Interactive Server). The build succeeded with 0 warnings and 0 errors.
Ran these checks in the browser against that app:
After a parent rerender, children bound to a method-group EventCallback stayed at 1 render. Children bound to a capturing lambda went to 2.
The ShouldRender sample didn't rerender for an unchanged order. It rerendered when the order version changed and when its own event handler ran.
IHandleEvent and the EventUtil non-rendering handlers (sync, async, and with MouseEventArgs) didn't trigger a render. An explicit StateHasChanged() still did.
Virtualize with an ItemsProvider over 100,000 items kept 21 rows in the DOM.
A/B evaluation in simulated Copilot agent-mode sessions
Each scenario ran with the same model (Claude Sonnet, also selectable in GitHub Copilot) and the same prompt. The only difference was whether the instruction file was attached, the way VS Code attaches instructions whose applyTo matches the edited files. I built the generated code (Blazor Server, .NET 10) and measured it in the browser.
Scenario
Without the instruction
With the instruction
Table of 5,000 orders: payload received per row click
Lands on order #2653 (ItemSize="40" with 38 px rows)
Lands on order #2501 (ItemSize matches the rendered row height)
Keyboard-scrollable container
No
Yes (tabindex="-1")
Pointer tracking, 100 moves in about 1 s: messages sent / bytes received
200 / 24.3 KB (@onpointermove bound directly)
40 / 5.7 KB (throttled in JavaScript)
Analytics click that changes nothing on screen
Triggers a render
No render (non-rendering handler); the click is still tracked
The evaluation also improved the instruction itself:
The first instructed run passed string parameters without @ (Customer="order.Customer"), so the literal text was rendered. The instruction now calls this out, and a second run rendered correctly.
The instructed pointer session had to look up the event-args overloads of the non-rendering handler helper, so those overloads are now part of the snippet.
Type of Contribution
New instruction file.
New prompt file.
New agent file.
New plugin.
New skill file.
New agentic workflow.
New canvas extension.
Update to existing instruction, prompt, agent, plugin, skill, workflow, or canvas extension.
Adds instructions/blazor-rendering-performance.instructions.md, applied to
.razor and .razor.cs files and based on the official ASP.NET Core Blazor
rendering performance best practices: when to optimize, change detection
rules, ShouldRender, virtualization, lightweight components at scale,
fixed cascading values, attribute splatting, throttled events,
non-rendering handlers, StateHasChanged usage, and delegate reuse in
large loops.
Regenerates docs/README.instructions.md.
…tion
A/B runs of simulated Copilot agent-mode sessions surfaced two gaps:
- The instructed session split a model into primitive parameters and
passed strings without @ (Customer="order.Customer"), which renders the
literal text. Show the @ prefix in the example, call it out explicitly,
and add it to the review checklist.
- A handler that needed MouseEventArgs had to fetch the generic EventUtil
overloads from the docs. Include Action<T> and Func<T, Task> overloads
in the snippet and show the explicit type argument usage.
- DateOnly and TimeOnly only became known immutable types for change
detection in .NET 10 (they're absent from ChangeDetection.cs in
release/8.0 and release/9.0), so list the .NET 8 baseline and call out
the .NET 10 addition.
- Recommend tabindex="0" with an accessible name for standalone
virtualized scroll regions so keyboard users can tab to them, and keep
tabindex="-1" for containers that code focuses explicitly.
Change detection excludes IntPtr/UIntPtr (nint/nuint), while nullable
forms of the supported value types are skipped too: a non-null T? boxes
as T and two null values compare as unchanged. Verified in a .NET 10 app:
children with int?, int? null, and DateTime? parameters kept 1 render
across 3 parent renders, while nint and TimeSpan children rerendered
every time.
Int128 and UInt128 report TypeCode.Object, so ChangeDetection treats
them as possibly changed, like nint and nuint. List the supported
integer widths explicitly instead of describing exclusions; the next
bullet already covers every other type.
This optimization only works as written on .NET 10+. In .NET 8 and 9, EventCallback.Equals compares the delegate by reference, while each instance method-group conversion creates a new delegate, so the child is still seen as changed on every parent render. Qualify the guidance or tell .NET 8/9 users to cache and reuse one EventCallback value.
This issue also appears in the following locations of the same file:
line 281
line 322
Qualify Virtualize fixed-height guidance for .NET 8–10
This is no longer release-independent guidance. In .NET 11, Virtualize measures actual item heights and maintains a running average, so ItemSize is an initial estimate and items no longer have to share one exact height. Since the file targets “.NET 8 or later,” qualify the fixed-height requirement as .NET 8–10 behavior.
… guidance
- On .NET 8 and 9, EventCallback.Equals compares delegate references
(value equality arrived in .NET 10), and every instance method-group
conversion allocates a new delegate. Recommend method groups only on
.NET 10+, and a callback created once with EventCallback.Factory.Create
on .NET 8 and 9. Verified: after 3 parent renders, a method-group child
rendered 4 times on .NET 9.0.20 and once on .NET 10.0.12, while a
cached callback rendered once on both.
- .NET 11 positions virtualized items using a running average of
measured heights, so the same-height requirement applies to .NET 8
through 10 and ItemSize becomes an initial estimate on .NET 11.
ChangeDetection.MayHaveChanged returns false before inspecting the runtime type when both the old and new values are null. Therefore an optional complex parameter (including ChildContent) that remains null does not force a child rerender, contrary to “only when every parameter is a known immutable type” and “every parent render.” Qualify this null case so Copilot doesn't add unnecessary ShouldRender logic.
ChangeDetection.MayHaveChanged returns false when the old and new values
are both null, before it looks at the runtime type. State that exception
so Copilot doesn't add ShouldRender logic for optional complex parameters
that stay null. Verified on .NET 10.0.12: after 3 parent renders, children
with a null object or null RenderFragment parameter rendered once, while
a child with a non-null object rendered 4 times.
The running-average change in .NET 11 doesn't remove Virtualize's equal-height layout requirement. The current virtualization documentation still states that all rendered items, including placeholders, must have identical heights and that varying heights prevents correct operation. Limiting this rule to .NET 8–10 can make Copilot generate broken .NET 11 lists; keep the invariant for every supported version and describe the running average separately.
Clarify lifecycle guidance for intermediate async state updates
ComponentBase renders when an async lifecycle task is first returned and when it completes, but it can't detect intermediate awaited state changes. This blanket prohibition can leave progress from a multi-step OnInitializedAsync or OnParametersSetAsync stale. Limit the no-call advice to ordinary/end-of-method use and include lifecycle methods in the intermediate-progress exception.
…renders
- The virtualization docs still require identical item and placeholder
heights on .NET 11, so keep that rule for every version and describe
the .NET 11 running average of measured heights separately.
- ComponentBase renders when an async event handler or lifecycle method
first yields and when it completes, but not between later awaits (see
RunInitAndSetParametersAsync). Limit the "don't call StateHasChanged"
advice to the end of handlers and lifecycle methods, and cover
intermediate progress in multi-step async lifecycle methods too.
…ragment field
- The "What's new in ASP.NET Core in .NET 11" release notes state that
Virtualize no longer assumes every item has the same height and adapts
to item sizes measured at runtime (dotnet/aspnetcore#66521). Limit the
same-height rule to .NET 8 through 10 and keep the single-vertical-stack
and spacer rules for every version.
- Rename the private RenderFragment field in the example to camelCase
(logLine) to match the other private fields in the guidance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Checklist
npm startand verified thatREADME.mdis up to date.mainbranch for this pull request.Description
Adds
instructions/blazor-rendering-performance.instructions.md(applyTo: '**/*.razor, **/*.razor.cs'). It turns the official ASP.NET Core Blazor rendering performance best practices into rules Copilot can apply while writing or reviewing Razor components:ShouldRender,SetParametersAsync, orIHandleEventto every component.ChangeDetection.cs. Records,DateTimeOffset,TimeSpan, collections, andRenderFragment/ChildContentalways trigger a child rerender.EventCallbacks instead of capturing lambdas, and aShouldRenderchange-key pattern that includes the stale-UI pitfall when the component also changes local state.Itemsvs.ItemsProvider,ItemSize,SpacerElement, placeholders, keyboard focus, and the fact thatVirtualizerenders no items during static SSR or prerendering.RenderFragmenttemplates, parameter count,IsFixedcascading values, attribute splatting, manualSetParametersAsyncas a last resort (small gain on .NET 10+), and literal sequence numbers inRenderTreeBuildercode.IHandleEvent, including theErrorBoundarycaveat), whenStateHasChangedis and isn't needed, pre-created delegates in large loops, and@key.How it differs from existing resources
blazor.instructions.mdcovers general Blazor conventions, and its performance guidance is five generic bullets (for example "useShouldRender()where appropriate"). This file is dedicated to rendering performance. It explains the mechanics that decide whether a rerender happens, the trade-offs between techniques, and when not to optimize.Validation
@key) and the ASP.NET Core reference source (ChangeDetection.cs,EventCallback.cs,EventCallbackFactory.cs,RenderTreeDiffBuilder.cs,Virtualize.cs).EventCallbackstayed at 1 render. Children bound to a capturing lambda went to 2.ShouldRendersample didn't rerender for an unchanged order. It rerendered when the order version changed and when its own event handler ran.IHandleEventand theEventUtilnon-rendering handlers (sync, async, and withMouseEventArgs) didn't trigger a render. An explicitStateHasChanged()still did.Virtualizewith anItemsProviderover 100,000 items kept 21 rows in the DOM.A/B evaluation in simulated Copilot agent-mode sessions
Each scenario ran with the same model (Claude Sonnet, also selectable in GitHub Copilot) and the same prompt. The only difference was whether the instruction file was attached, the way VS Code attaches instructions whose
applyTomatches the edited files. I built the generated code (Blazor Server, .NET 10) and measured it in the browser.OrderRowcomponents skipped)Virtualizespacers inside<tbody><div>(invalid table markup)<tr>(SpacerElement="tr")ItemSize="40"with 38 px rows)ItemSizematches the rendered row height)tabindex="-1")@onpointermovebound directly)The evaluation also improved the instruction itself:
stringparameters without@(Customer="order.Customer"), so the literal text was rendered. The instruction now calls this out, and a second run rendered correctly.Type of Contribution
Additional Notes
Source article: https://learn.microsoft.com/aspnet/core/blazor/performance/rendering. The guidance is identical in the .NET 10 and .NET 11 versions of that article.
By submitting this pull request, I confirm that my contribution abides by the Code of Conduct and will be licensed under the MIT License.