![]() |
VOOZH | about |
dotnet add package Blazor.WhyDidYouRender.Aspire --version 3.3.0
NuGet\Install-Package Blazor.WhyDidYouRender.Aspire -Version 3.3.0
<PackageReference Include="Blazor.WhyDidYouRender.Aspire" Version="3.3.0" />
<PackageVersion Include="Blazor.WhyDidYouRender.Aspire" Version="3.3.0" />Directory.Packages.props
<PackageReference Include="Blazor.WhyDidYouRender.Aspire" />Project file
paket add Blazor.WhyDidYouRender.Aspire --version 3.3.0
#r "nuget: Blazor.WhyDidYouRender.Aspire, 3.3.0"
#:package Blazor.WhyDidYouRender.Aspire@3.3.0
#addin nuget:?package=Blazor.WhyDidYouRender.Aspire&version=3.3.0Install as a Cake Addin
#tool nuget:?package=Blazor.WhyDidYouRender.Aspire&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. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
v3.3.0: Fixed critical Interactive Server session timing bug. Session IDs now use TraceIdentifier fallback when response has started, preventing crashes during prerendering. Aspire/OpenTelemetry integration extensions for Blazor.WhyDidYouRender. See CHANGELOG.md for details.