Blazor.WhyDidYouRender
3.2.1
dotnet add package Blazor.WhyDidYouRender --version 3.2.1
NuGet\Install-Package Blazor.WhyDidYouRender -Version 3.2.1
<PackageReference Include="Blazor.WhyDidYouRender" Version="3.2.1" />
<PackageVersion Include="Blazor.WhyDidYouRender" Version="3.2.1" />
<PackageReference Include="Blazor.WhyDidYouRender" />
paket add Blazor.WhyDidYouRender --version 3.2.1
#r "nuget: Blazor.WhyDidYouRender, 3.2.1"
#:package Blazor.WhyDidYouRender@3.2.1
#addin nuget:?package=Blazor.WhyDidYouRender&version=3.2.1
#tool nuget:?package=Blazor.WhyDidYouRender&version=3.2.1
Blazor.WhyDidYouRender
A powerful cross-platform performance monitoring and debugging tool for Blazor applications (Server, WebAssembly, SSR) that helps identify unnecessary re-renders and optimize component performance. Inspired by the React why-did-you-render library.
🌟 Features
- Cross-Platform Support: Works with Blazor Server, WebAssembly, and SSR
- Render Tracking: Monitor when and why components re-render
- Parameter Change Detection: Identify which parameter changes trigger re-renders
- State Tracking: Field-level change detection with
[TrackState]and[IgnoreState]attributes - Unnecessary Render Detection: Automatically detect and flag unnecessary re-renders
- Performance Metrics: Track render duration and frequency
- OpenTelemetry/Aspire Integration: Optional structured logs, traces, and metrics for observability dashboards
- Browser Console Logging: Rich logging output in browser developer tools
📦 Installation
dotnet add package Blazor.WhyDidYouRender
🚀 Quick Start
1. Register Services
using Blazor.WhyDidYouRender.Extensions;
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = builder.Environment.IsDevelopment();
config.Verbosity = TrackingVerbosity.Normal;
config.Output = TrackingOutput.Both;
config.TrackParameterChanges = true;
config.EnableStateTracking = true;
});
2. Initialize Services
var app = builder.Build();
// For Server/SSR
app.Services.InitializeSSRServices();
3. Initialize Browser Logging (Required for Console Output)
In a component that runs after JavaScript is available (e.g., MainLayout.razor or App.razor):
@inject IServiceProvider ServiceProvider
@inject IJSRuntime JSRuntime
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await ServiceProvider.InitializeWhyDidYouRenderAsync(JSRuntime);
}
}
}
Note: This step is required to see output in the browser's developer console. Without it, browser console logging won't work.
4. Track Components
@using Blazor.WhyDidYouRender.Components
@inherits TrackedComponentBase
<h1>Counter</h1>
<p>Count: @currentCount</p>
@code {
private int currentCount = 0; // Auto-tracked
}
📡 .NET Aspire / OpenTelemetry Integration
Enable rich observability with the Aspire dashboard:
// Add Aspire service defaults
builder.AddServiceDefaults();
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = true;
config.EnableOpenTelemetry = true;
config.EnableOtelLogs = true;
config.EnableOtelTraces = true;
config.EnableOtelMetrics = true;
});
What you'll see in Aspire:
- Traces:
WhyDidYouRender.Renderspans withwdyrl.*attributes - Metrics:
wdyrl.renders,wdyrl.rerenders.unnecessary,wdyrl.render.duration.ms - Structured Logs: Correlated to traces via
Activity.Current
See docs/observability.md for detailed setup and troubleshooting.
📚 Documentation
- Integration Guide - Step-by-step setup for different scenarios
- Configuration Guide - All configuration options explained
- API Documentation - Complete API reference
- Migration Guide - Upgrading from previous versions
- Examples & Best Practices - Usage patterns and optimization tips
- Observability Guide - Aspire/OpenTelemetry setup
🎯 Integration Scenarios
Scenario 1: New Blazor Server App
Step 1: Create New Project
dotnet new blazorserver -n MyBlazorApp
cd MyBlazorApp
Step 2: Install Package
dotnet add package Blazor.WhyDidYouRender
Step 3: Configure Services
Update Program.cs:
using Blazor.WhyDidYouRender.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// Add WhyDidYouRender
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = builder.Environment.IsDevelopment();
config.Verbosity = TrackingVerbosity.Normal;
config.Output = TrackingOutput.Both;
config.TrackParameterChanges = true;
config.TrackPerformance = true;
});
var app = builder.Build();
// Initialize WhyDidYouRender SSR services
app.Services.InitializeSSRServices();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Optional: Enable .NET Aspire / OpenTelemetry
// Add Aspire service defaults
builder.AddServiceDefaults();
builder.Services.AddWhyDidYouRender(config =>
{
config.EnableOpenTelemetry = true;
config.EnableOtelLogs = true;
config.EnableOtelTraces = true;
config.EnableOtelMetrics = true;
});
See docs/observability.md for verification steps and troubleshooting.
Step 4: Update Components
Update Pages/Counter.razor:
@page "/counter"
@using Blazor.WhyDidYouRender.Components
@using Blazor.WhyDidYouRender.Extensions
@inherits TrackedComponentBase
@inject IJSRuntime JSRuntime
@inject IServiceProvider ServiceProvider
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private bool browserLoggerInitialized = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !browserLoggerInitialized)
{
// Initialize WhyDidYouRender browser logging
await ServiceProvider.InitializeWhyDidYouRenderAsync(JSRuntime);
browserLoggerInitialized = true;
}
await base.OnAfterRenderAsync(firstRender);
}
private void IncrementCount()
{
currentCount++;
}
}
Scenario 2: Existing Blazor Server App
Step 1: Install Package
dotnet add package Blazor.WhyDidYouRender
Step 2: Add Service Registration
In your existing Program.cs, add the service registration:
// Add this after your existing service registrations
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = builder.Environment.IsDevelopment();
config.Verbosity = TrackingVerbosity.Normal;
config.Output = TrackingOutput.Both;
config.TrackParameterChanges = true;
});
Step 3: Gradually Migrate Components
Start with components you want to optimize:
// Before
@inherits ComponentBase
// After
@using Blazor.WhyDidYouRender.Components
@inherits TrackedComponentBase
Scenario 3: Blazor WebAssembly App
Step 1: Install Package
dotnet add package Blazor.WhyDidYouRender
Step 2: Configure Services
Update Program.cs:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazor.WhyDidYouRender.Extensions;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// Add WhyDidYouRender
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = builder.HostEnvironment.IsDevelopment();
config.Verbosity = TrackingVerbosity.Normal;
config.Output = TrackingOutput.BrowserConsole; // WebAssembly only supports browser console
config.TrackParameterChanges = true;
config.TrackPerformance = true;
});
await builder.Build().RunAsync();
⚙️ Configuration Examples
Development Configuration
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = true;
config.Verbosity = TrackingVerbosity.Verbose;
config.Output = TrackingOutput.Both;
config.TrackParameterChanges = true;
config.TrackPerformance = true;
config.IncludeSessionInfo = true;
});
Production Configuration
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = false; // Disable in production
});
Staging Configuration
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = true;
config.Verbosity = TrackingVerbosity.Minimal;
config.Output = TrackingOutput.Console;
config.TrackPerformance = true;
});
🔧 Advanced Integration
Custom Configuration Provider
public class WhyDidYouRenderConfigProvider
{
public static WhyDidYouRenderConfig GetConfiguration(IWebHostEnvironment env)
{
return new WhyDidYouRenderConfig
{
Enabled = env.IsDevelopment() || env.IsStaging(),
Verbosity = env.IsDevelopment() ? TrackingVerbosity.Verbose : TrackingVerbosity.Normal,
Output = env.IsDevelopment() ? TrackingOutput.Both : TrackingOutput.Console,
TrackParameterChanges = true,
TrackPerformance = true,
IncludeSessionInfo = env.IsDevelopment()
};
}
}
// Usage in Program.cs
builder.Services.AddWhyDidYouRender(
WhyDidYouRenderConfigProvider.GetConfiguration(builder.Environment)
);
Conditional Component Tracking
@using Blazor.WhyDidYouRender.Components
@if (ShouldTrack)
{
@inherits TrackedComponentBase
}
else
{
@inherits ComponentBase
}
@code {
private bool ShouldTrack =>
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
}
Browser logging initialization (explicit)
In interactive rendering or when using browser console logging, initialize WhyDidYouRender once JS is available (e.g., in MainLayout):
@inherits LayoutComponentBase
@inject IServiceProvider ServiceProvider
@inject IJSRuntime JSRuntime
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await ServiceProvider.InitializeAsync(JSRuntime);
}
}
}
Notes:
- WASM: You can alternatively call
await host.Services.InitializeWasmAsync(jsRuntime)in Program.cs; the component-based init above also works. - Server/SSR (interactive) with browser console: explicit
InitializeAsync(JSRuntime)prevents timing errors on first paint. - Server-only logging: set
config.Output = TrackingOutput.Consoleto disable browser console output.
🎨 Component Migration Patterns
Pattern 1: Gradual Migration
Start with high-traffic or problematic components:
// Identify components with performance issues first
@inherits TrackedComponentBase // Add to specific components
Pattern 2: Base Component Approach
Create a custom base component:
// Create MyTrackedComponentBase.cs
public abstract class MyTrackedComponentBase : TrackedComponentBase
{
// Add your common component logic here
}
// Use in components
@inherits MyTrackedComponentBase
Pattern 3: Conditional Tracking
Use preprocessor directives for conditional tracking:
#if DEBUG
@using Blazor.WhyDidYouRender.Components
@inherits TrackedComponentBase
#else
@inherits ComponentBase
#endif
Pattern 4: Use RenderTrackerService Directly
@inject RenderTrackerService RenderTracker
@code {
protected override void OnAfterRender(bool firstRender)
{
RenderTracker.TrackRender(this, "OnAfterRender", firstRender);
base.OnAfterRender(firstRender);
}
}
🚨 Common Issues & Solutions
Issue 1: Service Not Registered
Error: InvalidOperationException: Unable to resolve service for type 'RenderTrackerService'
Solution: Ensure you've called AddWhyDidYouRender() in your service registration:
builder.Services.AddWhyDidYouRender();
Issue 2: No Console Output
Problem: Not seeing any tracking information in browser console.
Solutions:
- Check that tracking is enabled:
config.Enabled = true - Verify output is set to browser:
config.Output = TrackingOutput.Both - Ensure components inherit from
TrackedComponentBase - Initialize browser logging:
await ServiceProvider.InitializeWhyDidYouRenderAsync(JSRuntime) - Open browser developer tools console
Issue 3: Too Much Logging
Problem: Console is flooded with tracking information.
Solutions:
- Reduce verbosity:
config.Verbosity = TrackingVerbosity.Minimal - Disable parameter tracking:
config.TrackParameterChanges = false - Disable in production:
config.Enabled = Environment.IsDevelopment()
Issue 4: Performance Impact
Problem: Tracking is affecting application performance.
Solutions:
- Disable in production environments
- Use selective component tracking
- Adjust tracking granularity
- Consider enabling .NET Aspire/OpenTelemetry for server-side observability
📊 Monitoring & Debugging
Browser Console Usage
- Open Developer Tools (F12)
- Navigate to Console tab
- Look for
[WhyDidYouRender]messages - Use console filters to focus on specific components
🔄 Migration Checklist
- Install Blazor.WhyDidYouRender package
- Add service registration in Program.cs
- Configure environment-specific settings
- Update target components to inherit from TrackedComponentBase
- Test in development environment
- Verify console output
- Configure for staging/production environments
- Document component tracking decisions
- Train team on usage and interpretation
📚 Next Steps
After successful integration:
- Monitor component render patterns
- Identify unnecessary re-renders
- Optimize component parameters and state management
- Use insights to improve application performance
- Consider enabling .NET Aspire/OpenTelemetry for ongoing monitoring
📄 License
This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
📋 Changelog
See CHANGELOG.md for a list of changes and version history.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components (>= 10.0.1)
- Microsoft.AspNetCore.Components.Web (>= 10.0.1)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Extensions (>= 2.2.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.1)
- Microsoft.JSInterop (>= 10.0.1)
-
net8.0
- Microsoft.AspNetCore.Components (>= 8.0.12)
- Microsoft.AspNetCore.Components.Web (>= 8.0.12)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Extensions (>= 2.2.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.JSInterop (>= 8.0.12)
-
net9.0
- Microsoft.AspNetCore.Components (>= 9.0.2)
- Microsoft.AspNetCore.Components.Web (>= 9.0.2)
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Extensions (>= 2.2.0)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.2)
- Microsoft.Extensions.DependencyInjection (>= 9.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.2)
- Microsoft.JSInterop (>= 9.0.2)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Blazor.WhyDidYouRender:
| Package | Downloads |
|---|---|
|
VaultForce.SharedClient
shared resources |
|
|
Blazor.WhyDidYouRender.Aspire
Aspire/OpenTelemetry integration extensions for Blazor.WhyDidYouRender - a cross-platform performance monitoring tool for Blazor applications. |
GitHub repositories
This package is not used by any popular GitHub repositories.
v3.2.1: Added multi-targeting support for .NET 10 LTS. Now supports .NET 8 LTS, .NET 9, and .NET 10 LTS. Fixed CI/CD packaging. See CHANGELOG.md for full details.