Blazor Sections and Streaming Rendering in .NET 8

.NET 8 introduced two features that fundamentally change how Blazor pages load: sections for composing layout content from child pages, and streaming rendering for progressively loading data-heavy pages. Together, they make static server-side rendering feel fast and flexible.

Sections: Layout Content from Child Pages

Sections let a child page inject content into a named slot defined in the layout. Think of it as a more structured version of layout sections from MVC Razor views.

Define an outlet in your layout:

MainLayout.razor
@inherits LayoutComponentBase

<header>
    <nav>My App</nav>
    <SectionOutlet SectionName="page-actions" />
</header>

<main>
    @Body
</main>

Then, from any page, provide content for that section:

razor
@page "/orders"

<SectionContent SectionName="page-actions">
    <button @onclick="Export">Export CSV</button>
    <button @onclick="CreateNew">New Order</button>
</SectionContent>

<h1>Orders</h1>
<OrderList />

@code {
    private void Export() { /* ... */ }
    private void CreateNew() { /* ... */ }
}

When this page renders, the buttons appear in the header area defined by the layout. Navigate to a different page and those buttons disappear — the section content is tied to the page lifecycle.

Typed Section IDs

For compile-time safety, use SectionId instead of string names:

SharedSections.cs
public static class SharedSections
{
    public static readonly SectionId PageActions = new();
    public static readonly SectionId Sidebar = new();
}
razor
<!-- In layout -->
<SectionOutlet SectionId="SharedSections.PageActions" />

<!-- In page -->
<SectionContent SectionId="SharedSections.PageActions">
    <button>Action</button>
</SectionContent>

This prevents typos and gives you IDE navigation support.

Streaming Rendering

Streaming rendering solves the slow-page problem. Without it, a page that loads data in OnInitializedAsync blocks the entire response until the data is ready. With streaming rendering, Blazor sends the initial HTML immediately (with placeholder content) and streams updates as async operations complete.

Enable it with the [StreamRendering] attribute:

razor
@page "/dashboard"
@attribute [StreamRendering]

<h1>Dashboard</h1>

@if (stats is null)
{
    <p>Loading statistics...</p>
}
else
{
    <div class="stats-grid">
        <StatCard Title="Revenue" Value="@stats.Revenue.ToString("C")" />
        <StatCard Title="Orders" Value="@stats.OrderCount.ToString()" />
        <StatCard Title="Customers" Value="@stats.CustomerCount.ToString()" />
    </div>
}

@code {
    private DashboardStats? stats;

    protected override async Task OnInitializedAsync()
    {
        stats = await DashboardService.GetStatsAsync(); // Takes 2 seconds
    }
}

Here's what happens:

  1. The browser receives the page immediately with "Loading statistics..." visible.
  2. When GetStatsAsync completes, Blazor streams the updated HTML to the browser.
  3. The DOM is patched in place — no full page reload, no JavaScript required.

This works with static SSR only. The mechanism uses HTTP chunked transfer encoding to stream HTML fragments to the browser.

Multiple Async Operations

Streaming rendering shines when a page has several independent data loads:

razor
@page "/overview"
@attribute [StreamRendering]

<div class="grid">
    <section>
        <h2>Recent Orders</h2>
        @if (orders is null)
        {
            <LoadingSpinner />
        }
        else
        {
            <OrderTable Orders="orders" />
        }
    </section>

    <section>
        <h2>Notifications</h2>
        @if (notifications is null)
        {
            <LoadingSpinner />
        }
        else
        {
            <NotificationList Items="notifications" />
        }
    </section>
</div>

@code {
    private List<Order>? orders;
    private List<Notification>? notifications;

    protected override async Task OnInitializedAsync()
    {
        var ordersTask = OrderService.GetRecentAsync();
        var notificationsTask = NotificationService.GetUnreadAsync();

        orders = await ordersTask;
        // First stream update: orders appear

        notifications = await notificationsTask;
        // Second stream update: notifications appear
    }
}

Each await triggers a stream update, so whichever data load completes first will appear first. If you want both to appear simultaneously, use Task.WhenAll:

Example.cs
protected override async Task OnInitializedAsync()
{
    var ordersTask = OrderService.GetRecentAsync();
    var notificationsTask = NotificationService.GetUnreadAsync();

    await Task.WhenAll(ordersTask, notificationsTask);

    orders = ordersTask.Result;
    notifications = notificationsTask.Result;
    // Single stream update: both appear together
}

Combining Sections and Streaming

Sections and streaming work independently but complement each other. A common pattern is a page that provides section content immediately (like action buttons in the header) while streaming the main data:

razor
@page "/reports"
@attribute [StreamRendering]

<SectionContent SectionName="page-actions">
    <button disabled="@(data is null)" @onclick="Export">Export</button>
</SectionContent>

@if (data is null)
{
    <p>Generating report...</p>
}
else
{
    <ReportTable Data="data" />
}

The section content renders with the initial response. The export button starts disabled and becomes active once the streamed data arrives.

Limitations

Streaming rendering only works with static SSR — it's not applicable to interactive Server or WebAssembly modes, which use SignalR or run client-side respectively. It also can't stream updates after the HTTP response has completed, so it's a one-shot mechanism during initial page load.

Sections only work within a single render tree. You can't use SectionContent in a component rendered with a different render mode than its SectionOutlet.

Despite these constraints, both features fill important gaps in building responsive, well-structured Blazor applications.