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
| Model | Code runs | First load | Interaction | Needs |
|---|---|---|---|---|
| Blazor Server | On the server, over a SignalR circuit | Fast | Depends on round-trip latency | A persistent connection per user |
| Blazor WebAssembly | In the browser | Slow, downloads the runtime | Immediate after load | A browser capable of WASM |
| Interactive Auto | Server first, then WebAssembly on later visits | Fast then offline capable | Best of both after warm-up | Both hosting models |
| Hybrid (MAUI) | On the device, native shell | Application install | Immediate | A 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
OnInitializedAsynccode 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.
Related
Razor Pages and MVC views Real-time features with SignalR
Last refreshed 2026-09-18.