Blazor.WhyDidYouRender
3.3.0
dotnet add package Blazor.WhyDidYouRender --version 3.3.0
NuGet\Install-Package Blazor.WhyDidYouRender -Version 3.3.0
<PackageReference Include="Blazor.WhyDidYouRender" Version="3.3.0" />
<PackageVersion Include="Blazor.WhyDidYouRender" Version="3.3.0" />
<PackageReference Include="Blazor.WhyDidYouRender" />
paket add Blazor.WhyDidYouRender --version 3.3.0
#r "nuget: Blazor.WhyDidYouRender, 3.3.0"
#:package Blazor.WhyDidYouRender@3.3.0
#addin nuget:?package=Blazor.WhyDidYouRender&version=3.3.0
#tool nuget:?package=Blazor.WhyDidYouRender&version=3.3.0
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 (3)
Showing the top 3 NuGet packages that depend on Blazor.WhyDidYouRender:
| Package | Downloads |
|---|---|
|
GreatIdeaz.trellispark.UX.WASM.Components
Package Description |
|
|
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.3.0: Fixed critical session timing bug in Interactive Server mode. Session IDs now use TraceIdentifier fallback when response has started or session is unavailable, preventing "The session cannot be established after the response has started" errors during prerendering. See CHANGELOG.md for full details.