.NET 9 Blazor: Full-Stack Development with Blazor United in 2026

.NET 9 Blazor United combines static SSR, Server, and WebAssembly render modes into one full-stack framework. A practical tutorial covering render modes, streaming rendering, constructor injection, and production-ready patterns.

.NET 9 Blazor United full-stack development with multiple render modes

.NET 9 Blazor United merges three rendering strategies — static server-side rendering (SSR), interactive Server, and WebAssembly — into a single cohesive full-stack framework. This unified model, introduced in .NET 8 and refined in .NET 9, eliminates the need to choose between separate Blazor hosting models before writing a single line of code.

Blazor United in a Nutshell

Blazor United allows mixing static SSR, Server interactivity, and WebAssembly interactivity within the same application. Each component independently declares its render mode, enabling fine-grained control over performance and interactivity per page.

Setting Up a .NET 9 Blazor Web App

The blazorweb template in .NET 9 scaffolds a project pre-configured for all three render modes. The setup requires the .NET 9 SDK (9.0.100 or later) and creates both the server project and the client (WASM) project in one command.

bash
# Create a new Blazor Web App with all render modes enabled
dotnet new blazorweb -n FullStackApp --interactivity Auto --all-interactive false
cd FullStackApp
dotnet run

The --interactivity Auto flag enables both Server and WebAssembly render modes. Setting --all-interactive false keeps the default render mode as static SSR, so interactivity is opted into per-component rather than globally.

Understanding Blazor Render Modes in .NET 9

Every component in a Blazor Web App adopts a render mode that determines where it runs, whether it supports interactivity, and how it communicates with the server. .NET 9 provides four distinct modes.

| Render Mode | Hosting | Interactivity | Best For | |---|---|---|---| | Static SSR | Server | None | Marketing pages, docs, SEO content | | Interactive Server | Server via SignalR | Full | Dashboards, CRUD, forms | | Interactive WebAssembly | Browser via WASM | Full | Offline, heavy client-side logic | | Interactive Auto | Server then WASM | Full | Best of both — fast load + independence |

The render mode is set at the component level using the @rendermode directive attribute. This per-component granularity means a single page can mix static and interactive sections.

Components/Pages/Dashboard.razorcsharp
@page "/dashboard"
@rendermode InteractiveServer

<PageTitle>Dashboard</PageTitle>

<h1>Real-Time Metrics</h1>

<!-- This component renders interactively via SignalR -->
<MetricsChart />

<!-- Static content below renders as plain HTML -->
<footer>Updated every 5 seconds</footer>

A .NET 9 API addition — ComponentBase.RendererInfo — lets components detect their current render mode at runtime, enabling conditional logic based on execution context.

Components/Shared/AdaptiveComponent.razorcsharp
@if (RendererInfo.IsInteractive)
{
    <button @onclick="HandleClick">Interactive Action</button>
}
else
{
    <a href="/fallback">Static Fallback Link</a>
}

@code {
    private void HandleClick()
    {
        // Runs only in interactive modes (Server or WASM)
    }
}

Streaming Rendering for Async-Heavy Pages

Static SSR pages that fetch data asynchronously can block the response until all tasks complete. Streaming rendering solves this by sending placeholder HTML immediately, then pushing final content as data arrives. In .NET 9, the [StreamRendering] attribute no longer requires the true parameter — it defaults to enabled.

Components/Pages/Products.razorcsharp
@page "/products"
@attribute [StreamRendering]

<PageTitle>Product Catalog</PageTitle>

@if (products is null)
{
    <p>Loading products...</p>
}
else
{
    <div class="product-grid">
        @foreach (var product in products)
        {
            <ProductCard Item="@product" />
        }
    </div>
}

@code {
    private List<Product>? products;

    // Data loads asynchronously; streaming pushes updates to the browser
    protected override async Task OnInitializedAsync()
    {
        products = await ProductService.GetAllAsync();
    }
}

Streaming rendering relies on HTTP/1.1 chunked transfer encoding, supported by all modern browsers. The placeholder content renders instantly, and the final markup replaces it without a full page reload.

Constructor Injection in Blazor Components

.NET 9 introduces constructor injection for Blazor components, complementing the existing @inject directive and [Inject] attribute. Combined with C# 12 primary constructors, service dependencies become part of the component's type signature.

Components/Pages/OrderHistory.razorcsharp
@page "/orders"
@rendermode InteractiveServer

<h2>Order History</h2>

@if (orders is not null)
{
    <table>
        <thead>
            <tr><th>Order ID</th><th>Date</th><th>Total</th></tr>
        </thead>
        <tbody>
            @foreach (var order in orders)
            {
                <tr>
                    <td>@order.Id</td>
                    <td>@order.PlacedAt.ToShortDateString()</td>
                    <td>@order.Total.ToString("C")</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    // Constructor injection via primary constructor
    [Inject] public required IOrderService OrderService { get; set; }
    [Inject] public required NavigationManager Nav { get; set; }

    private List<Order>? orders;

    protected override async Task OnInitializedAsync()
    {
        orders = await OrderService.GetRecentOrdersAsync();
    }
}

Constructor injection makes dependencies explicit and simplifies unit testing. Services registered in Program.cs are resolved automatically, including keyed services added via builder.Services.AddKeyedScoped<T>().

Ready to ace your .NET interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Interactive Auto Mode — Best of Both Worlds

The Auto render mode delivers the fastest perceived load time. On the first visit, the component runs on the server via SignalR while the WebAssembly runtime downloads in the background. On subsequent visits, execution moves entirely to the browser.

Components/Pages/Chat.razorcsharp
@page "/chat"
@rendermode InteractiveAuto

<h2>Live Chat</h2>

<div class="chat-messages">
    @foreach (var msg in messages)
    {
        <div class="message">@msg.Author: @msg.Text</div>
    }
</div>

<input @bind="newMessage" @onkeydown="HandleKey" placeholder="Type a message..." />

@code {
    [Inject] public required IChatService ChatService { get; set; }

    private List<ChatMessage> messages = new();
    private string newMessage = string.Empty;

    protected override async Task OnInitializedAsync()
    {
        messages = await ChatService.GetRecentMessagesAsync();
    }

    private async Task HandleKey(KeyboardEventArgs e)
    {
        if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(newMessage))
        {
            await ChatService.SendAsync(newMessage);
            newMessage = string.Empty;
        }
    }
}

Auto mode requires that the component's dependencies and logic work in both server and WASM contexts. Services should be abstracted behind interfaces and registered in both the server's and the client's DI containers.

Reconnection and Resilience in .NET 9

.NET 9 rewrites Blazor Server's reconnection logic with an exponential backoff strategy. Instead of retrying at fixed intervals, the framework starts with fast retries and gradually increases the delay — reducing server load during outages while recovering quickly from brief disruptions.

Key improvements:

  • Navigating back to a tab triggers an immediate reconnection attempt
  • If the server has already released the circuit, the page refreshes automatically
  • The default UI shows "Rejoining the server..." with a progress indicator
  • Retry intervals are configurable via Blazor.start()
wwwroot/app.js — Custom reconnection configurationjavascript
Blazor.start({
    circuit: {
        reconnectionOptions: {
            retryIntervalMilliseconds: (retryNumber) => {
                // Exponential backoff: 200ms, 400ms, 800ms, max 30s
                return Math.min(200 * Math.pow(2, retryNumber), 30000);
            },
            maxRetries: 15
        }
    }
});

Static SSR with Selective Interactivity

The recommended architecture for most Blazor Web Apps in 2026 starts with static SSR as the default, adding interactivity only where needed. The [ExcludeFromInteractiveRouting] attribute (new in .NET 9) marks pages that must remain static — useful for pages that rely on HTTP cookies or the request/response cycle.

Components/Pages/Privacy.razorcsharp
@page "/privacy"
@attribute [ExcludeFromInteractiveRouting]

<PageTitle>Privacy Policy</PageTitle>

<!-- This page always renders as static HTML -->
<!-- Even if global interactivity is enabled -->
<article>
    <h1>Privacy Policy</h1>
    <p>Last updated: April 2026</p>
    <!-- Content -->
</article>

This pattern works well with MapStaticAssets, a new .NET 9 middleware that compresses and fingerprints static files at build time, replacing the older UseStaticFiles approach with optimized delivery out of the box.

Performance Gains with .NET 9 AOT Compilation

.NET 9 AOT (Ahead-of-Time) compilation reduces WebAssembly payload sizes by up to 40% compared to .NET 8. Blazor WASM startup is 25% faster according to Google Lighthouse benchmarks. These gains apply automatically when publishing with AOT enabled.

bash
# Publish with AOT for production
dotnet publish -c Release -p:RunAOTCompilation=true

WebSocket compression is also enabled by default for interactive Server components. A Content Security Policy (frame-ancestors: 'self') is applied automatically to mitigate compression-related attack vectors.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Conclusion

  • Blazor United in .NET 9 eliminates the hosting model decision by combining static SSR, Server, and WebAssembly in a single project
  • Set render modes per-component with @rendermode to optimize each page independently — static for SEO, Server for dashboards, Auto for hybrid scenarios
  • Use streaming rendering with [StreamRendering] to eliminate loading delays on async-heavy pages
  • Constructor injection (new in .NET 9) makes component dependencies explicit and testable
  • Auto render mode delivers fast first paint via Server, then shifts to WASM on repeat visits for full client independence
  • Exponential backoff reconnection reduces dropped connections and eliminates manual page refreshes
  • AOT compilation cuts WASM payload sizes by 40% and startup time by 25%
  • Start every Blazor Web App with static SSR as the default, then layer interactivity where the UX demands it

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Tags

#dotnet
#blazor
#aspnet-core
#webassembly
#full-stack

Share

Related articles