Making Blazor Feel Instant: Streaming Rendering and Enhanced Navigation in .NET 8
Blazor's streaming rendering and enhanced navigation in .NET 8 make server-rendered pages feel like an SPA — progressive HTML delivery, preserved scroll and state, no full reloads. Here's how they work and where the edges are.
06 Sept 2026, 03:20 UTC

The problem: fast pages that still feel slow
You have a Blazor dashboard page that queries a database, calls an API, and renders a table. The whole round trip takes 800 ms. That's not terrible — but the user stares at a blank screen (or a spinner) for all of it, and every link click triggers a full page reload that resets scroll position and wipes component state. The page is technically fine and perceptually sluggish.
.NET 8 added two features that attack exactly this: streaming rendering, which sends HTML to the browser progressively instead of waiting for async work to finish, and enhanced navigation, which intercepts link clicks and patches only the changed DOM instead of reloading the page. Used together, they make a server-rendered Blazor app feel like an SPA — without shipping a WebAssembly bundle or holding a SignalR circuit open for every visitor.
Streaming rendering: send the skeleton first
Normally, a prerendered page waits for OnInitializedAsync to complete before any HTML leaves the server. With streaming rendering enabled, Blazor sends the static parts of the page immediately — layout, headings, placeholder markup — then streams the async sections as they complete, patching them into the DOM over the same response.
Opt in on a page with one attribute:
@page "/orders"
@attribute [StreamRendering(true)]
<h1>Orders</h1>
@if (orders is null)
{
<p>Loading orders…</p>
}
else
{
<ul>
@foreach (var o in orders)
{
<li>@o.Id — @o.Total</li>
}
</ul>
}
@code {
private List<Order>? orders;
protected override async Task OnInitializedAsync()
{
orders = await OrderService.GetRecentAsync(); // slow call
}
}The heading and the "Loading orders…" placeholder arrive in the first chunk; the populated list arrives in a later chunk once the await completes. The user sees meaningful content in ~50 ms instead of 800 ms. Importantly, the prerender still runs first, so the initial HTML is complete from the perspective of crawlers and social-preview scrapers — the streaming part is progressive enhancement, not a client-side-only trick.
You can verify it's working in the browser's network tab: the document response uses chunked transfer encoding and stays open while chunks arrive, rather than completing in one shot.
Enhanced navigation: SPA feel without the SPA plumbing
Enhanced navigation intercepts clicks on internal links, fetches the target page with fetch(), diffs the response against the current DOM, and patches only what changed. Scroll position, focus, form input, and component state survive the transition. In a Blazor Web App template this is on by default for content inside the router; you can control it per link:
<a href="/orders" data-enhance-nav="false">Full reload please</a>Or force a full load from code when you genuinely need one:
NavigationManager.NavigateTo("/orders", forceLoad: true);The combination is where it pays off: a list page streams its skeleton instantly, rows fill in as data arrives, and clicking through to a detail page and back feels instantaneous because nothing reloads and the list's state is still there.
Where the sharp edges are
A few constraints matter before you lean on this:
- .NET 8 or later only. Neither feature exists in .NET 7 or earlier.
- Streaming requires prerendering. A purely client-rendered component with no prerender pass has nothing to stream from.
- Lifecycle surprise:
OnAfterRenderAsyncfires on the initial load but not on subsequent enhanced navigations. Put per-navigation logic inOnParametersSetAsyncinstead. - SignalR message limits. On Interactive Server, the default maximum message size (32 KB) can bite with very large streamed chunks; raise
HubOptions.MaximumReceiveMessageSizeif you hit it. - Caching granularity. CDNs can cache the initial prerendered HTML, but individual stream chunks aren't separately cacheable.
- Browser quirks. Back/forward cache behavior with enhanced navigation varies — test Safari and Firefox explicitly, not just Chrome.
A practical checklist
- Create or open a Blazor Web App on .NET 8+ with an interactive render mode.
- Add
@attribute [StreamRendering(true)]to your slowest data-backed page and confirm chunked responses in dev tools. - Navigate between pages and confirm no full reload occurs and scroll/state persist.
- Audit any
OnAfterRenderAsynclogic that assumed full page loads. - Test back/forward navigation in Safari and Firefox before shipping.
The takeaway: you no longer have to choose between the simplicity of server rendering and the responsiveness of an SPA. Streaming rendering fixes perceived first paint; enhanced navigation fixes everything after it. Both are one-attribute opt-ins, and the fastest way to build intuition is to add them to your slowest page and watch the network tab.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.