# Blazor Performance & Advanced Patterns ## Rendering Optimization ### ShouldRender Override Control when components re-render to prevent unnecessary rendering cycles. ```csharp @page "/optimized" @code { private int count = 0; private string value = "test"; protected override bool ShouldRender() { // Only render if value changed, not if count changed // This component doesn't display count directly return false; // Skip render } private void IncrementCount() { count++; // Component won't re-render, child component won't re-render either } } ``` ### Tracking Changed Fields ```csharp @page "/tracker"

Name: @name

Age: @age

@code { private string? name; private int age; private bool nameChanged = false; private bool ageChanged = false; protected override bool ShouldRender() { if (!nameChanged && !ageChanged) { return false; } nameChanged = false; ageChanged = false; return true; } private void UpdateName() { name = "New Name"; nameChanged = true; } private void UpdateAge() { age = 30; ageChanged = true; } } ``` ### Key Directive for List Items ```csharp @page "/list" @foreach (var item in items) { } @code { private List items = []; private void AddItem() { items = items.Prepend(new Item { Id = Guid.NewGuid(), Name = "New" }).ToList(); } } public class Item { public Guid Id { get; set; } public string? Name { get; set; } } ``` **Why @key matters:** - Helps Blazor's diffing algorithm match old components to new items - Prevents component state loss during list reordering - Improves performance with large lists ### IDisposable for Cleanup ```csharp @implements IAsyncDisposable @inject IJSRuntime JS private IJSObjectReference? module; private Timer? timer; protected override async Task OnInitializedAsync() { timer = new Timer(_ => UpdateAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { module = await JS.InvokeAsync( "import", "./myScript.js"); } } async ValueTask IAsyncDisposable.DisposeAsync() { timer?.Dispose(); if (module is not null) { await module.DisposeAsync(); } } ``` ## Virtualization Virtualize large lists to render only visible items. ### Basic Virtualization ```csharp @page "/large-list" @using Microsoft.AspNetCore.Components.Web.Virtualization

@item.Id - @item.Name

@code { private List largeList = []; protected override void OnInitialized() { // Generate 100,000 items largeList = Enumerable.Range(1, 100000) .Select(i => new Item { Id = i, Name = $"Item {i}" }) .ToList(); } } ``` ### Async Virtualization (Infinite Scroll) ```csharp @page "/infinite-scroll" @using Microsoft.AspNetCore.Components.Web.Virtualization
@item.Name
@code { private async ValueTask> LoadItems( ItemsProviderRequest request) { // Simulate loading from server var startIndex = request.StartIndex; var count = request.Count; var items = await Service.GetItemsAsync(startIndex, count); // Return items and total count for scrollbar sizing return new ItemsProviderResult(items, totalItemCount: 1000000); } } ``` **Parameters:** - `Items` - Static list of items to virtualize - `ItemsProvider` - Async method to load items on demand - `OverscanCount` - Extra items to render outside viewport (default 3) - `ItemSize` - Estimated height for scrollbar calculation ## JavaScript Interop ### Invoke JavaScript from C # ```csharp @inject IJSRuntime JS @code { private async Task CallJavaScript() { // Simple call - no return value await JS.InvokeVoidAsync("console.log", "Hello from Blazor"); // With return value var result = await JS.InvokeAsync("myFunction", arg1, arg2); // Generic call with any return type var data = await JS.InvokeAsync("loadData"); } } ``` ### JS Module Isolation (Recommended) ```csharp // Component.razor @implements IAsyncDisposable @inject IJSRuntime JS
@code { private ElementReference element; private IJSObjectReference? module; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Import JS module module = await JS.InvokeAsync( "import", "./scripts/chart.js"); // Call exported function await module.InvokeVoidAsync("initChart", element); } } async ValueTask IAsyncDisposable.DisposeAsync() { if (module is not null) { await module.DisposeAsync(); } } } /* scripts/chart.js */ export function initChart(element) { const canvas = element.querySelector('#chart'); // Initialize chart library } ``` ### Invoke C# from JavaScript ```csharp // Component.razor @implements IAsyncDisposable @inject IJSRuntime JS @code { private IJSObjectReference? module; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { module = await JS.InvokeAsync( "import", "./scripts/interop.js"); // Pass C# object reference to JS var objRef = DotNetObjectReference.Create(this); await module.InvokeVoidAsync("setupInterop", objRef); } } [JSInvokable] public async Task HandleJSEvent(string data) { Console.WriteLine($"JS called C#: {data}"); // Update component state StateHasChanged(); } async ValueTask IAsyncDisposable.DisposeAsync() { if (module is not null) { await module.DisposeAsync(); } } } /* scripts/interop.js */ let dotnetHelper; export function setupInterop(dotnetRef) { dotnetHelper = dotnetRef; // Call C# method from JS document.addEventListener('click', async (e) => { await dotnetHelper.invokeMethodAsync('HandleJSEvent', 'User clicked'); }); } ``` ### Error Handling in Interop ```csharp @code { private async Task SafeInvokeAsync() { try { await JS.InvokeVoidAsync("riskyFunction"); } catch (JSException jsEx) { Console.WriteLine($"JS error: {jsEx.Message}"); } catch (OperationCanceledException) { Console.WriteLine("JS call was cancelled"); } } } ``` ## Lazy Loading Load assemblies and components on demand. ### Lazy-Loaded Component Routes ```csharp

Loading...

@code { private List? additionalAssemblies; protected override async Task OnInitializedAsync() { additionalAssemblies = new(); } private async Task OnNavigateAsync(NavigationContext context) { // Load admin assembly only when accessing /admin if (context.Path.StartsWith("admin")) { var adminAssembly = await JS.InvokeAsync( "fetch", "./_framework/admin.wasm"); additionalAssemblies!.Add(Assembly.Load(adminAssembly)); } } } ``` ## WASM Performance Best Practices ### AOT Compilation ```xml true ``` Benefits: - No JIT compilation at runtime - Faster startup time - ~20% larger download - Production recommended ### Trimming ```xml true ``` Benefits: - Removes unused code - ~40% smaller download - May cause runtime errors if reflection-based code removed - Test thoroughly in Release build ### Compression ```xml true ``` Server-side (in Program.cs): ```csharp app.UseResponseCompression(); builder.Services.AddResponseCompression(opts => { opts.Filters.Add(new GzipCompressionProvider()); opts.Filters.Add(new BrotliCompressionProvider()); }); ``` ### Minimize JavaScript Interop ```csharp // INEFFICIENT - Many JS calls for (int i = 0; i < 1000; i++) { await JS.InvokeVoidAsync("updateUI", i); } // EFFICIENT - Single JS call with batch data var updates = Enumerable.Range(0, 1000).ToList(); await JS.InvokeVoidAsync("updateUIBatch", updates); ``` ## Error Boundaries Handle component errors gracefully. ```csharp @page "/error-demo"

Error

@ex.Message

@code { private ErrorBoundary? errorBoundary; private async Task ResetError() { await errorBoundary!.RecoverAsync(); } } ``` ## CSS Isolation Scope CSS to specific components. ```html

@Title

.container { background-color: blue; padding: 20px; } h1 { color: white; font-size: 24px; } ``` **Benefits:** - No global namespace pollution - Component-specific styling - CSS automatically scoped to component - Compiled into assembly ## Best Practices Summary ### Performance - Use `@key` on list items - Override `ShouldRender()` to prevent unnecessary renders - Use virtualization for large lists - Minimize JavaScript interop calls - Enable AOT compilation and trimming for WASM ### JavaScript Interop - Use module isolation pattern - Always dispose JS module references - Handle JS exceptions properly - Only call JS in `OnAfterRender` with firstRender check - Minimize interop calls for performance ### Architecture - Keep components simple and focused - Move logic to services - Use cascading values for shared state - Implement IDisposable for cleanup - Validate authorization on server side ### User Experience - Show loading states during async operations - Provide error feedback - Use AuthorizeView for conditional rendering - Implement error boundaries - Test on slow connections --- **Related Resources:** See [components-lifecycle.md](components-lifecycle.md) for component disposal patterns. See [state-management-events.md](state-management-events.md) for state update optimization.