.NET 10 InteractiveAuto /visitor-stats

December 5th 2025

⏳ Static - Non-Interactive (Pre-Rendered)


I recently upgraded

this

web app to .NET 10, based on the .NET 10 Blazor Web App template with the render mode globally set to InteractiveAuto and pre-rendering enabled. Blazor’s rendering pipeline is powerful, but it also introduces some interesting behaviors when you first visit a page versus when you return to it.


If you're interested in learning more, here’s the relevant Microsoft documentation



When you load /visitor-stats for the very first time
When the user first loads the page, the server prerenders the component and sends static HTML to the browser. At this point there is no active circuit yet. With InteractiveAuto once the browser establishes interactivity, Blazor chooses a runtime—usually Server (first visit) and WASM on later visits—and then the circuit (or WASM runtime) is created. During the initial connection to the server the WebAssembly version of the app is downloaded and cached in the background. On later visits, when those WASM assemblies are available, Blazor can run this page as Interactive WebAssembly so the interactivity is handled entirely in the browser instead of on the server. If pre-rendering is enabled, the server still pre-renders the HTML first and then the app transitions to interactive WebAssembly once the client runtime is ready.

If the component runs an async task in OnInitializedAsync (for example, fetching data from a service), the pre-rendering phase waits for that task to complete before sending the HTML. You can change this behaviour by decorating the page with @attribute [StreamRendering] , which allows the server to stream partial HTML immediately while the async work continues.

Sometimes opting to disable pre-rendering makes sense and may be recommended when you don't care about SEO, accessibility, open graph meta tags or that initial load time. Disabling pre-rendering saves you the headache of having to special case data list initialization code and JS interop calls to only run when the page is RendererInfo.IsInteractive == true.

In .NET 10 we now have the declaritive [PersistentState] attribute which replaces the PersistentComponentState service injection and hefty PersistingComponentStateSubscription, ApplicationState.TryTakeFromJson, and ApplicationState.PersistAsJson callback registration previously required in .NET 8 to persist data from pre-rendering to interactivity.

How the Visitor Tracking Works
I have a middleware that parses GET requests to this /visitor-stats page and safely logs that VisitorInfo object which includes some basic info such as browser, device, OS, response time and adds a generated visitor-id cookie to the request.

Time Browser Device OS Resp. (ms) Visitor ID

Today 11:00 AM

Unknown Bot/Crawler Unknown 1 6fc69d1f

Today 10:05 AM

Safari iPhone macOS 2 0edc13e5

Today 9:42 AM

Chrome Windows PC Windows 1 ae92fdbc

Today 9:20 AM

Firefox Windows PC Windows 3 2c50c394

Today 9:12 AM

Chrome Windows PC Windows 6 4877e920

Today 8:54 AM

Safari iPhone macOS 19 24bb5002

Today 7:55 AM

Safari iPhone macOS 1 3526ff05

Today 6:19 AM

Chrome Windows PC Windows 53 357057ba

Today 4:57 AM

Safari iPhone macOS 21 a0cbd1f5

Today 2:43 AM

Safari iPhone macOS 13 05741f79

What Happens on Future Visits
Blazor’s Enhanced Navigation intercepts link clicks and avoids full page reloads. The previously-rendered DOM is preserved, and the runtime (wasm or server) simply hydrates the page into interactive mode. Because no real HTTP GET occurs, pre-rendering server-side lifecycle methods are not re-run. For this page specifically, a custom middleware logs only real GET requests—and enhanced navigation does not generate them.

Comments

An unhandled error has occurred. Reload 🗙

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 reload the page.