Skip to content
Open source · MIT · net10.0 / net11.0

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 · MainLayout.razor
// Program.cs
builder.Services.AddBitBevToolsServerServices();

@* MainLayout.razor *@
<BevToolsPanel />

It is running here. Press CtrlShiftD or the ⚙ BevTools badge, open the Timeline, then click:

one onclick → Home.Click event, one render of Home
No source changespublic hooks: the component activator, HTTP handlers, loggers
Never fakes datawhat it cannot observe, it says it cannot
Every render modeWebAssembly, Server, Auto and static SSR
Dormant in productionon in Development only; off, it registers nothing

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.

Dashboard →

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.

Orders →

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.

Checkout →

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.

Problem 17 →

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.

Problem 16 →

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

01

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.

Getting started →
// Server project
builder.Services.AddBitBevToolsServerServices();

// .Client project
builder.Services.AddBitBevToolsServices(o =>
    o.Enabled = builder.HostEnvironment.IsDevelopment());
02

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.

Checkout →
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>();
03

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.

QuestionBrowser dev toolsBevTools
Which components exist, with what parameters?DOM elements onlyThe 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 visibleA cause per render: event, parent, navigation, StateHasChanged
HTTP calls of a Blazor Server appNot visible - they are made by the serverEvery IHttpClientFactory request with the UI event that sent it
JS interop on Blazor ServerSignalR framesCalls per identifier, with duration, result and component
What is registered in DI?Not visibleLifetimes, dependencies, captive dependencies and cycles
Is the circuit healthy?A WebSocketLifecycle, 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.

Extending →

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

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.

An unhandled error has occurred. Reload 🗙