Interactive UI with Blazor

Server, WebAssembly and hybrid differ in where the code runs. That single decision drives latency, deployment and what a component can do.

The hosting models

ModelCode runsFirst loadInteractionNeeds
Blazor ServerOn the server, over a SignalR circuitFastDepends on round-trip latencyA persistent connection per user
Blazor WebAssemblyIn the browserSlow, downloads the runtimeImmediate after loadA browser capable of WASM
Interactive AutoServer first, then WebAssembly on later visitsFast then offline capableBest of both after warm-upBoth hosting models
Hybrid (MAUI)On the device, native shellApplication installImmediateA desktop or mobile target
// Program.cs on the server
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

var app = builder.Build();
app.MapRazorComponents<App>()
   .AddInteractiveServerRenderMode()
   .AddInteractiveWebAssemblyRenderMode();
  • Blazor Server keeps component state on the server, so memory and the connection count are the limiting resources. A few thousand concurrent users is a real ceiling per instance.
  • Blazor WebAssembly can be slow to start on a cold cache. Trimming and AOT cut size and raise performance, both at the cost of build time.
  • A server circuit must be resilient to a dropped connection; the reconnect UI is not optional in production.
  • Prerendering runs the component on the server first, which means your OnInitializedAsync code runs twice unless you guard it.
  • Shared components live in a project referenced by both the server and the WebAssembly host, which is what makes Interactive Auto possible.

Components, parameters and forms

@* OrderList.razor *@
@inject IOrderApi Api
@rendermode InteractiveServer

<h3>Orders</h3>

@if (_loading)
{
    <p>Loading...</p>
}
else if (_orders is null)
{
    <p role="alert">Could not load orders.</p>
}
else
{
    <table>
        <thead><tr><th>Id</th><th>Total</th><th></th></tr></thead>
        <tbody>
        @foreach (var order in _orders)
        {
            <tr>
                <td>@order.Id</td>
                <td>@order.Total.ToString("0.00")</td>
                <td>
                    <button class="btn" @onclick="() => Cancel(order.Id)"
                            disabled="@order.Cancelled">Cancel</button>
                </td>
            </tr>
        }
        </tbody>
    </table>
}

@code {
    [Parameter] public int CustomerId { get; set; }
    [Parameter] public EventCallback<int> OnCancelled { get; set; }

    private IReadOnlyList<Order>? _orders;
    private bool _loading = true;

    protected override async Task OnParametersSetAsync()
    {
        _loading = true;
        try
        {
            _orders = await Api.GetOrdersAsync(CustomerId);
        }
        catch (HttpRequestException)
        {
            _orders = null;
        }
        finally
        {
            _loading = false;
        }
    }

    private async Task Cancel(int id)
    {
        await Api.CancelAsync(id);
        await OnCancelled.InvokeAsync(id);
    }
}
@* A form with validation, using EditForm and data annotations *@
<EditForm Model="_input" OnValidSubmit="SaveAsync" FormName="order">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <label for="sku">SKU</label>
    <InputText id="sku" @bind-Value="_input.Sku" />
    <ValidationMessage For="() => _input.Sku" />

    <button type="submit" disabled="@_saving">Save</button>
</EditForm>

@code {
    private OrderInput _input = new();
    private bool _saving;

    private async Task SaveAsync()
    {
        _saving = true;
        try { await Api.SaveAsync(_input); }
        finally { _saving = false; }
    }
}
⚠️
Blazor Server streams the user's interaction back to the server, so authorisation must be enforced on the server for every operation. A hidden button is not a permission: assume the user can invoke any method in any component they can reach.

FAQ

Why does my component load twice?
Because of prerendering: the component is rendered on the server, then rendered again when the interactive circuit starts. Guard the expensive work or move it into a lifecycle method that only runs interactively.
When is Blazor WebAssembly the right choice?
When the client must keep working offline, when the interaction is intensive and latency matters, or when you want to offload computation from the server. The cost is size and cold-start time.

Razor Pages and MVC views Real-time features with SignalR

Last refreshed 2026-09-18.