Dev tools for Blazor,
inside your app.
A live component tree, a profiler that says why each component rendered, a timeline that links
every click to what it caused, and inspectors for state, HTTP, JS interop, errors, DI and circuits -
added with one line in Program.cs and no change to your components.
dotnet add package Bit.BevTools
// Program.cs builder.Services.AddBitBevToolsServerServices(); @* MainLayout.razor *@ <BevToolsPanel />
It is running here. Press CtrlShiftD or the ⚙ BevTools badge, open the Timeline, then click:
Capabilities
The questions it answers
Each panel answers a question that logs and breakpoints answer slowly. Every page of this site is a small app built to ask one of them - open the panel on it and look.
Why did it render?
The component tree with parameters, cascading values, state and services - and for every render its
cause: an event, a parent re-render, a navigation or StateHasChanged. Pick a component
in the page, or watch them flash as they render.
What did this keystroke cost?
A profiler per instance and per type, each render batch as a cost tree, the rules that spot render storms, cascades and renders forced by a parent - and a comparison with the recording before your fix.
Products →What happened when I clicked?
One timeline for renders, batches, events, navigation, state, HTTP, JS, errors and circuits, with each entry linked to what caused it and to what it caused.
Counter →Which request failed, and who sent it?
Every IHttpClientFactory request with status, duration, headers (redacted), the UI event
and the component that sent it, and per endpoint the p95 - including the requests a Blazor Server app
makes on the server.
What changed in my state?
State containers that implement IBevStateProvider get a history of named actions with
before/after diffs and the click behind each one.
Which route is this, and who is signed in?
The page, template, route values and query, the route table, each navigation and what it rendered, the guards that can stop one, redirect loops - and the user's roles and claims.
Routing & sign-in →Which JS call is slow?
Interop calls per identifier with durations, failures and the component that made them, plus the JS → .NET callbacks and the browser's storage, redacted.
JS interop →Where did that exception come from?
Errors from rendering, handlers, HTTP, interop and the browser, each with its component, its source location and the ten timeline entries before it.
Errors →Is my circuit blocked?
On Blazor Server: the circuit's lifecycle, connection state, time spent on each inbound message and the reconnect attempts the browser reports.
Circuit →Does my form check what it should?
Every EditForm with what validates it, what shows its messages and each validation's
outcome - and a check of the page for fields and buttons a screen reader cannot name.
What keeps a disposed component alive?
Components the renderer let go that the garbage collector cannot free, with what holds each one: an
event handler on a service, a lambda, a timer, a DotNetObjectReference.
Is my DI set up right?
Every registration with its lifetime and dependencies, and the mistakes that only fail later: a singleton capturing a scoped service, a circular dependency.
Problem 8 →In practice
Three steps, and two of them are optional
Register it
One call in the server project, one in the .Client project of an Interactive Auto app.
It switches itself on in Development only.
// Server project builder.Services.AddBitBevToolsServerServices(); // .Client project builder.Services.AddBitBevToolsServices(o => o.Enabled = builder.HostEnvironment.IsDevelopment());
Expose your state
A state container that implements IBevStateProvider gets a history of named
changes with diffs. Register it once and the State panel shows it.
public sealed class CartState : IBevStateProvider { public string Name => "CartState"; public event Action<BevStateChangeInfo>? Changed; public object? GetSnapshot() => this; // … Changed?.Invoke(new BevStateChangeInfo("Add", product.Name)); } builder.Services.AddBevToolsStateProvider<CartState>();
Keep secrets out of it
Members, headers, query values and storage keys that look sensitive are redacted at capture time. Mark the rest yourself, or hide a member or a component entirely.
Problem 7 →public sealed class UserSession { // Redacted by its name. public string AccessToken { get; set; } = "…"; [BevToolsSensitive] public string Nickname { get; set; } = "…"; }
Comparison
Why not the browser's dev tools?
The browser sees the DOM, the requests the browser makes and JavaScript. A Blazor app's questions are about components, .NET state, the server and the renderer - and on Blazor Server most of it never reaches the browser at all.
| Question | Browser dev tools | BevTools |
|---|---|---|
| Which components exist, with what parameters? | DOM elements only | The component tree, parameters, cascading values, state and services |
| Which component rendered this element? | The element, not its component | ⌖ Pick it in the page, or select it in the Elements panel with the browser extension |
| Why did this component render? | Not visible | A cause per render: event, parent, navigation, StateHasChanged |
| HTTP calls of a Blazor Server app | Not visible - they are made by the server | Every IHttpClientFactory request with the UI event that sent it |
| JS interop on Blazor Server | SignalR frames | Calls per identifier, with duration, result and component |
| What is registered in DI? | Not visible | Lifetimes, dependencies, captive dependencies and cycles |
| Is the circuit healthy? | A WebSocket | Lifecycle, connection state, inbound message time, reconnects |
Where to start
Choose your path
New to BevTools?
Install, register, place the panel - and see your own app from the inside.
Getting started →Want to practise first?
Twenty-four real problems are planted in this app, each with the steps from the symptom to its cause.
Problems to diagnose →Writing a library?
Contribute panels, state providers, diagnostic rules, commands and timeline events through IBevToolsExtension - this site registers one.
Questions
Frequently asked
Does it end up in production?
Only if you ask it to. It is enabled when the host environment is Development unless
options.Enabled says otherwise, renders nothing during prerendering, and when disabled
registers no activator, handler or logger - a referenced but disabled BevTools measured at about 2 %
in the benchmark, inside its noise. This site turns it on explicitly so that you can try it. To have
it in production without anyone downloading it until they ask, Bit.BevTools.Loader opens
it on demand - on Blazor Server only for the people an Authorize callback lets in.
Do I have to look at it inside the app?
No. The companion window (⧉ in the panel header) shows every BevTools runtime of the page in a window of its own, and opens exported sessions offline; the browser extension adds a Blazor panel to Chromium and Firefox DevTools. Both read the same data through the same redaction as the panel.
Can CI use what it finds?
Yes. Export the session after a test run and bevtools check session.json fails the build
on the rules' findings; bevtools compare before.json after.json fails it when a component
renders more often, or clearly longer, than before. The same checks run in a test through
BevSessionCheck and BevProfileComparison.
Which .NET versions and render modes?
.NET 10 and 11, in WebAssembly, Server, Interactive Auto and static SSR.
How does it see inside the app without changing it?
Through public hooks - the component activator, an HTTP message handler filter, a logger provider, a circuit handler - plus the framework's own activities and meters, and optional reflection over Blazor internals that reports a capability as unavailable rather than failing when a member is missing.
What about passwords and tokens?
Redacted when they are captured: by name (password, token,
authorization, …), by [BevToolsSensitive], and in the browser before storage
values reach .NET. Request and response bodies are never read, nothing is sent anywhere, and an export
contains exactly what the panels show.
What does it cost?
Roughly 0.4 µs and 0.4 KB per component render while recording, measured by a benchmark in the repository, with every buffer bounded. The About panel reports what BevTools itself is costing the running app.
Why does this site's header show up in the component tree?
Because it is part of the app. The header, the docs layout and the footer of this site are ordinary components, so BevTools lists them, profiles them and attributes their JS calls like any other - only BevTools' own UI is left out.
Find the cause, not just the symptom
One package, one line in Program.cs, one tag in your layout.
dotnet add package Bit.BevTools