.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 1:16 AM

Safari iPhone macOS 14 2c9fbc17

Today 12:48 AM

Chrome Linux Desktop Linux 1 241c9513

Sat, Dec 13th

Safari iPhone macOS 4 421e6cbd

Sat, Dec 13th

Chrome Windows PC Windows 27 4877e920

Sat, Dec 13th

Chrome Mac Desktop macOS 46 5dbe4613

Sat, Dec 13th

Safari iPhone macOS 1 f46f4b9e

Sat, Dec 13th

Firefox Windows PC Windows 21 ed649daf

Sat, Dec 13th

Unknown Bot/Crawler Unknown 1 cfb0da40

Sat, Dec 13th

Safari iPhone macOS 12 d351c7f8

Sat, Dec 13th

Safari iPhone macOS 2 3839c36c

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.