![]() |
VOOZH | about |
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" />Directory.Packages.props
<PackageReference Include="Blazor.WhyDidYouRender" />Project file
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.0Install as a Cake Addin
#tool nuget:?package=Blazor.WhyDidYouRender&version=3.3.0Install as a Cake Tool
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.
[TrackState] and [IgnoreState] attributesdotnet add package Blazor.WhyDidYouRender
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;
});
var app = builder.Build();
// For Server/SSR
app.Services.InitializeSSRServices();
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.
@using Blazor.WhyDidYouRender.Components
@inherits TrackedComponentBase
<h1>Counter</h1>
<p>Count: @currentCount</p>
@code {
private int currentCount = 0; // Auto-tracked
}
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:
WhyDidYouRender.Render spans with wdyrl.* attributeswdyrl.renders, wdyrl.rerenders.unnecessary, wdyrl.render.duration.msActivity.CurrentSee for detailed setup and troubleshooting.
dotnet new blazorserver -n MyBlazorApp
cd MyBlazorApp
dotnet add package Blazor.WhyDidYouRender
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();
// Add Aspire service defaults
builder.AddServiceDefaults();
builder.Services.AddWhyDidYouRender(config =>
{
config.EnableOpenTelemetry = true;
config.EnableOtelLogs = true;
config.EnableOtelTraces = true;
config.EnableOtelMetrics = true;
});
See for verification steps and troubleshooting.
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++;
}
}
dotnet add package Blazor.WhyDidYouRender
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;
});
Start with components you want to optimize:
// Before
@inherits ComponentBase
// After
@using Blazor.WhyDidYouRender.Components
@inherits TrackedComponentBase
dotnet add package Blazor.WhyDidYouRender
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();
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = true;
config.Verbosity = TrackingVerbosity.Verbose;
config.Output = TrackingOutput.Both;
config.TrackParameterChanges = true;
config.TrackPerformance = true;
config.IncludeSessionInfo = true;
});
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = false; // Disable in production
});
builder.Services.AddWhyDidYouRender(config =>
{
config.Enabled = true;
config.Verbosity = TrackingVerbosity.Minimal;
config.Output = TrackingOutput.Console;
config.TrackPerformance = true;
});
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)
);
@using Blazor.WhyDidYouRender.Components
@if (ShouldTrack)
{
@inherits TrackedComponentBase
}
else
{
@inherits ComponentBase
}
@code {
private bool ShouldTrack =>
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
}
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:
await host.Services.InitializeWasmAsync(jsRuntime) in Program.cs; the component-based init above also works.InitializeAsync(JSRuntime) prevents timing errors on first paint.config.Output = TrackingOutput.Console to disable browser console output.Start with high-traffic or problematic components:
// Identify components with performance issues first
@inherits TrackedComponentBase // Add to specific components
Create a custom base component:
// Create MyTrackedComponentBase.cs
public abstract class MyTrackedComponentBase : TrackedComponentBase
{
// Add your common component logic here
}
// Use in components
@inherits MyTrackedComponentBase
Use preprocessor directives for conditional tracking:
#if DEBUG
@using Blazor.WhyDidYouRender.Components
@inherits TrackedComponentBase
#else
@inherits ComponentBase
#endif
RenderTrackerService Directly@inject RenderTrackerService RenderTracker
@code {
protected override void OnAfterRender(bool firstRender)
{
RenderTracker.TrackRender(this, "OnAfterRender", firstRender);
base.OnAfterRender(firstRender);
}
}
Error: InvalidOperationException: Unable to resolve service for type 'RenderTrackerService'
Solution: Ensure you've called AddWhyDidYouRender() in your service registration:
builder.Services.AddWhyDidYouRender();
Problem: Not seeing any tracking information in browser console.
Solutions:
config.Enabled = trueconfig.Output = TrackingOutput.BothTrackedComponentBaseawait ServiceProvider.InitializeWhyDidYouRenderAsync(JSRuntime)Problem: Console is flooded with tracking information.
Solutions:
config.Verbosity = TrackingVerbosity.Minimalconfig.TrackParameterChanges = falseconfig.Enabled = Environment.IsDevelopment()Problem: Tracking is affecting application performance.
Solutions:
[WhyDidYouRender] messagesAfter successful integration:
This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the file for details.
Contributions are welcome! Please see for guidelines.
See for a list of changes and version history.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 2 NuGet packages that depend on Blazor.WhyDidYouRender:
| Package | Downloads |
|---|---|
|
GreatIdeaz.trellispark.UX.WASM.Components
Package Description |
|
|
Blazor.WhyDidYouRender.Aspire
Aspire/OpenTelemetry integration extensions for Blazor.WhyDidYouRender - a cross-platform performance monitoring tool for Blazor applications. |
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.