Start here
Problems to diagnose
Each problem is real, deliberately left in this app's code, and discoverable with BevTools. Follow the steps; the point is to reach the cause faster than with logs and breakpoints. Open the panel with CtrlShiftD or the ⚙ BevTools badge in the bottom corner.
1. The whole dashboard re-renders twice a second
Symptom: Dashboard feels busy; Profiler shows StatsCard, UserProfile and RecentOrders rendering constantly.
- On the dashboard, tick "clock re-renders whole dashboard". In the Components tab, tick flash renders: every card flashes twice a second.
- BevTools → Profiler: sort by renders.
Dashboardand every child climb together. - Select the Revenue
StatsCard: "Why did it render?" says Parent Dashboard re-rendered (parameters re-applied). Keep it selected for a few ticks: the render list marks each one forced because no observable parameter value changed (compare with the Visitors card, whoseValuedoes change), and Forced re-renders counts them. - Select
Dashboard: cause is StateHasChanged outside an event; the Timeline shows it right after eachLiveClockrender. - Diagnostics reports Renders driven only by the parent for the StatsCards, all three in one finding: every one of their renders was caused by Dashboard, never by their own state. Fix: do not call StateHasChanged on the parent from the clock tick (or override ShouldRender in the cards).
2. Typing in the product search renders 120 rows per keystroke
Symptom: Input feels laggy on Server; Profiler shows ProductRow × 120 per keystroke.
- Type a few characters. Diagnostics: a render cascade - a keystroke's batch renders the list and every row in it, 121 components while nothing is filtered out - and the
ProductRows re-rendered only becauseProductListdid. Drag the max price slider: every step notifies, andProductListrenders in a storm. - Timeline: each oninput → FilterBar (@bind) event is followed by a
FilterStatechange and one batch with 121 renders. - Profiler → Recent render batches → select a keystroke's batch: its cost tree is
ProductListwithProductRow ×120 instancesunder it. - Fix candidates: debounce
FilterState, overrideShouldRenderinProductRow, use@keyand immutable rows. Problem 21 measures one.
3. One HTTP request per keystroke, none cancelled
Symptom: Network panel fills with GET /api/search?q=… requests; results arrive out of order.
- Enable "search the API on every keystroke", type quickly.
- Network: a request per keystroke, each triggered by an oninput event (see the Trigger column). Diagnostics: burst of requests to one endpoint - GET …/api/search?q: 6 requests in 2 s, the event that sent them, and - when they overlap - how many started before the previous one completed.
- Fix: debounce and pass a
CancellationTokenthat is cancelled by the next keystroke.
4. Slow and failing endpoints
Symptom: Orders take seconds; the flaky endpoint fails intermittently; a 404 goes unnoticed.
- Use the buttons on the Orders page.
- Network: durations and statuses; Errors: HTTP failures with the preceding UI event; Diagnostics: slow HTTP, repeated failures, duplicate requests (the burst of 8).
- Start the 4 s load and press Cancel loading: Network marks the request cancelled - not failed - and Errors stays as it was.
5. Chatty and slow JS interop
Symptom: On Server every interop call is a round trip; 120 calls in a loop take a while.
- Click "Chatty loop" and "Slow call". JS Interop panel: per-identifier counts, max duration, originating component.
- Diagnostics: excessive interop, slow interop. Fix: batch into one JS function; move loops to JS.
6. Where did that exception come from?
Symptom: An error surfaces; which component and which click caused it?
- Trigger any error. Errors panel: source, component, source location (from the stack), the UI event that preceded it and the last ten timeline entries.
7. Sensitive data must not leak into a debugging tool
Symptom: Tokens and keys exist in state, headers and storage.
- Components → UserProfile → Injected services →
Session:AccessTokenandNicknameare redacted, andSupportNotes([BevToolsIgnore]) is not listed at all; State → AppState:ApiKeyis redacted; Network → headers show a redacted Authorization; Browser → storage redactsdemo:auth_token(written by the JS Interop page).
8. A singleton captures a scoped service; two services depend on each other
Symptom: Works on the developer machine, leaks user state in production; the cycle throws only when resolved.
- BevTools → Services: search
LeakyCache(captive dependency) andReportService(cycle). Diagnostics lists both with the evidence.
9. The circuit freezes and eventually dies (Server only)
Symptom: UI stops responding for a moment; later the circuit ends and Blazor's error bar appears.
- Block the circuit; Timeline shows Slow inbound message; Circuit panel shows max inbound processing time. Block it three times within a minute: Diagnostics reports 3 inbound messages blocked the circuit.
- Open the companion window (⧉), then kill the circuit: the panel stops with the circuit it renders over, while the companion's Network → WebSockets shows the circuit's last message (
JS.Error) and its socket closing, and its status line says the runtime is disconnected.
10. What changed in the cart, and which click did it?
Symptom: State mutated somewhere; find the action and the before/after values.
- Add and remove items. State → CartState: change history with named actions, diffs and a link to the triggering UI event.
11. The product list is loaded twice
Symptom: GET /api/products runs on every visit to Products, and once more right after the page appears.
- Open Products with a full page load (reload the tab). The page is prerendered on the server, then becomes interactive;
CategorySummaryloads the products inOnInitializedAsync, which runs for both. - Diagnostics: GET /api/products ran while prerendering and again when the page became interactive. The prerendering session ended with the response; BevTools handed its summary over through persistent component state.
- Runtime → Prerendering: what the server rendered, how long it took and which requests it made. Fix:
[PersistentState]on the loaded list (.NET 10), orPersistentComponentState.
12. Which route is this, and what does this input run?
Symptom: A page shows up under a URL; which component, which template, which handler?
- Routing: the current page, its
@pagetemplate, the route values and the query string (sensitive values redacted), the navigation history and the route table with each page's authorization and render mode. The Routing & sign-in page has route values and a query to try. - Press ⌖ Pick and click the search box: the inspector opens on
FilterBar. Event handlers & callbacks lists the<input>'soninputand the method it runs, read from its latest render tree, with a link to its line. SelectProductListto see the callbacks it passes to each row. - The tree marks where a render mode starts a subtree: ⚡ on
Routes, Server or WASM - the runtime this visit of an Auto page got. Runtime → Prerendering names the mode as declared, InteractiveAuto.
13. Who is signed in, and what does the app know about them?
Symptom: Authorization behaves unexpectedly; which user, which roles, which claims, since when?
- Not signed in.
- State → Authentication: the user, roles and claims, with a change history (anonymous → authenticated) and its timeline entry. The
access_tokenclaim is redacted; the others are shown. - Routing → Authentication: the same at a glance, above the route table, whose Authorization column lists each page's
[Authorize]requirements (no page of this demo has any).
14. How much does the circuit carry? (Server only)
Symptom: Typing feels slow on a slow network; how big is each update?
- On a Server circuit, type in the product search. Network → WebSockets: every
JS.RenderBatchthe circuit sent, its size, and how long the browser took to apply it; BevTools' own reports are counted apart. - The panel renders over the same circuit while it is open; batches that changed only the panel are counted as BevTools' own, and the companion window (⧉ in the panel header) avoids them altogether. Diagnostics flags render batches of 128 KB or more.
15. Share a profile, not a screenshot
Symptom: A colleague needs to see what the page did, in detail, without your machine.
- About → Export performance trace, then open the file in ui.perfetto.dev or the Chrome DevTools Performance panel: render batches with every component render nested inside, UI events, requests and interop on their own tracks, arrows from each click to what it caused.
- About → Export session (JSON); the companion window (⧉) opens such a file offline with Open file. Copy report (Markdown) is the summary for an issue.
16. Hiding the price alert does not free it
Symptom: Memory grows every time the alert is shown and hidden; nothing errors, nothing looks wrong.
- On the products page, tick and untick price alert a few times: each
PriceAlertis disposed by the renderer. - BevTools → Runtime → Disposed components still alive → Collect garbage and check:
PriceAlertsurvives, held by FilterState (injected) → Changed: lambda in PriceAlert.OnInitialized, which captures the component. Diagnostics reports it too after the runtime's own full collections, without forcing one. The Store tab (the demo's extension) counts one more subscriber per alert. - Fix: keep the handler in a field, implement
IDisposableand unsubscribe inDispose. A component that only its parent's previous render tree still holds is not reported: Blazor keeps that tree for diffing until the parent renders again.
17. The newsletter form accepts an empty address
Symptom: Subscribe succeeds with an empty field, although the model says the address is required.
- On the checkout page, press Subscribe with the newsletter field empty: it subscribes "".
- BevTools → Forms: the newsletter form's Validated by is nothing, next to the model's rules (Email: Required, EmailAddress); the shipping form above is validated by DataAnnotationsValidator and shows its messages through ValidationSummary. The Timeline records each validation: Form validated: NewsletterModel is valid, "nothing validates it".
- Diagnostics: NewsletterSignup: NewsletterModel's validation attributes are never checked. Fix: add
<DataAnnotationsValidator />inside the EditForm.
18. The account page flickers before it shows
Symptom: Opening the account page shows the login page and back, several times.
- Open the account page: it redirects to the login page, which redirects back, a few times. Run the loop again on that page repeats it.
- BevTools → Diagnostics: Navigation loop: Navigate to /loop/account ↔ Navigate to /loop/login, every navigation programmatic (made from code, not by a click); Routing → Navigation history lists each hop and what it rendered.
- Fix: make the two redirect conditions agree (the login page must not send someone back whom the account page sends to it).
19. The orders table makes 25 requests
Symptom: Loading the orders is quick, then the details column fills in row by row while the server is busy.
- On the orders page, tick each row loads its own details and press Load orders.
- BevTools → Network → Endpoints:
GET /api/orders/{id}, 25 requests, 25 different URLs, with their p50 and p95; select it for the requests themselves. Each one's Sent from is OrderRow.OnInitializedAsync via ProductApi.GetOrderDetailsAsync. - Diagnostics: GET http://…/api/orders/{id}: one request per item (25 ids), during onclick → Orders.Load (lambda). Fix: return the details with the list (or from one endpoint that takes the ids), or load a row's details when it is opened.
20. The same error, a new message every time
Symptom: The error list grows by one line per click, each about another order.
- On the errors page, press Open a missing order a few times (on Server, Recover in between).
- BevTools → Errors lists each as its own error: Unhandled exception rendering component: Order 4711 was not found., … Order 1302 was not found.… → Groups shows them as one: KeyNotFoundException: Unhandled exception rendering component: Order {n} was not found. (the message the framework logs), with how many messages and occurrences, the components and the source location; select it for the errors themselves.
- Diagnostics: KeyNotFoundException: … Order {n} was not found. occurred 3 times with 3 different messages. Fix: handle the missing order where it is looked up (a not-found state instead of an exception).
21. Did the fix make typing cheaper?
Symptom: A change should stop the rows re-rendering; prove it, and keep it from coming back.
- On the products page, Profiler → Reset, type lamp, then About → Export session (JSON).
- Tick the fix: rows skip renders that would change nothing (
ProductRow.ShouldRendercompares the row's values), Reset, type lamp again. - Profiler → Compare… with the file:
ProductRowimproved, its renders before and after. In CI:bevtools compare before.json after.jsonfails the build when a component renders more often than in the baseline.
22. A screen-reader user cannot apply a coupon
Symptom: The coupon box looks fine, but a screen reader announces an unlabelled field and a button with no name.
- On the checkout page, put something in the cart, then BevTools → Browser → Check the page.
- Two findings, each naming
Checkoutas the component that rendered the element, with its WCAG criterion and Show to outline it: a form field with no label (its placeholder is not one) and a button with no accessible name (its ✓ is hidden from screen readers). Diagnostics repeats them. - Fix: a
<label>for the field, anaria-label="Apply coupon"(or visible text) on the button.
23. Something asks before every navigation
Symptom: Clicking any link opens a confirmation; which component asks, and what does it run?
- On the routing page, tick ask before leaving this page, then click a link: a confirmation appears.
- BevTools → Routing → Navigation guards: a
NavigationLockinRouting, which also asks before the browser leaves the site, and the callback it runs before internal navigation,Routing.ConfirmLeaving.
24. A picture that never loads, a frame that stays blank
Symptom: Nothing throws in .NET, yet part of the page is missing.
- On the errors page, press Load a missing image and Embed a frame the CSP forbids.
- BevTools → Errors: Failed to load <img> with its address, source resource; Content Security Policy blocked … with the directive (
frame-src), source csp. The browser bridge reports both; .NET never saw them.