Render Modes
Static server rendering, interactive server over a WebSocket, or WebAssembly in the browser, chosen per component.
@rendermode InteractiveServer | InteractiveWebAssembly | InteractiveAutoA render mode answers one question: where does this component's C# run, and does it keep running after the page arrives. In .NET 10 you set it per component with @rendermode, and a single app can mix all of them.
The four modes
Static server rendering is the default. The component runs once on the server, produces HTML, and is finished. Links and form posts still work, event handlers do not. Interactive Server (InteractiveServer) keeps the component alive on the server in a circuit, a per-user session connected to the browser by a WebSocket, which is a connection that stays open in both directions. Clicks travel up, HTML diffs travel down. Interactive WebAssembly (InteractiveWebAssembly) downloads the .NET runtime and your assemblies into the browser and runs the component there. Interactive Auto (InteractiveAuto) uses the server for the first visit while WebAssembly downloads in the background, then uses WebAssembly from then on.
The trade-offs
| Mode | Click latency | First-visit download | Survives a lost network |
|---|---|---|---|
| Static server | Full page or form post | Smallest | No |
| Interactive Server | One network round trip | Small | No |
| Interactive WebAssembly | None, it is local | Several megabytes | Yes, once loaded |
Interactive Server also holds every user's component state in server memory, so user count and memory cost move together.
What trips people up
Interactive components are prerendered first, so their code runs twice: once on the server to produce the initial HTML, then again where they are interactive.
Example
// Program.cs - a Blazor Web App that allows both interactive modes.
// Before .NET 8 these were separate project types called Blazor Server and
// Blazor WebAssembly. One app with render modes replaced that split.
using BlazorSample.Components;
var builder = WebApplication.CreateBuilder(args);
// One call per mode the app is allowed to use. Leave a call out and
// @rendermode for that mode throws when a component asks for it.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
var app = builder.Build();
app.MapStaticAssets(); // .NET 9 and later; older templates used UseStaticFiles
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
// Routable components that live in the WebAssembly client project.
.AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly);
app.Run();When to use it
- Keep a marketing page on static server rendering for speed and make only its newsletter widget interactive.
- Move a drawing canvas to WebAssembly so dragging does not depend on network latency.
- Decide whether a 2,000-user admin tool can afford one server-side circuit per signed-in user.
More examples
Per-component modes, and turning prerendering off
This is the shape most real apps end up with: static HTML for the page, interactivity only where it is needed. Turn prerendering off for a component that cannot render usefully without the browser, such as one that measures an element's size, otherwise its first render produces markup that is immediately replaced.
@* Components/Pages/Dashboard.razor *@
@page "/dashboard"
@* No @rendermode here: this page is static server rendered. *@
<h1>Dashboard</h1>
@* One island of interactivity on an otherwise static page. *@
<RefreshButton @rendermode="InteractiveServer" />
@* Heavy client-side work, run in the browser. The constructor form is the
longhand of @rendermode="InteractiveWebAssembly"; it takes prerender,
which stops the component rendering once on the server first. *@
<ChartCanvas @rendermode="@(new InteractiveWebAssemblyRenderMode(prerender: false))" />
@* The short names InteractiveServer and InteractiveWebAssembly come from
@using static Microsoft.AspNetCore.Components.Web.RenderMode in
_Imports.razor. Without it, write RenderMode.InteractiveServer. *@Global interactivity, set once in App.razor
Setting the mode on Routes makes the whole app interactive, which the templates call global interactivity. It is the simplest option and the most expensive one: every page now holds a circuit. Prefer it for an internal tool behind a login, not for a public site with crawlers and casual visitors.
@* Components/App.razor - the root component, holds the HTML document *@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<base href="/" />
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
@* Routes holds the Router, so every routable component below it
inherits this render mode. A child cannot opt back out. *@
<Routes @rendermode="InteractiveServer" />
<script src="_framework/blazor.web.js"></script>
</body>
</html>Rendering differently while the page is not yet interactive
Between the prerendered HTML arriving and the connection being established, the controls are visible but inert. Disabling them from RendererInfo.IsInteractive removes the window where a user clicks and nothing happens. Use the same check to skip work that only makes sense once a browser is really there.
@* Components/SearchBox.razor *@
@rendermode InteractiveServer
@* RendererInfo is .NET 9 and later. IsInteractive is false during
prerendering and for static server rendering. *@
<input @bind="_term" disabled="@(!RendererInfo.IsInteractive)" />
<button @onclick="Search" disabled="@(!RendererInfo.IsInteractive)">
@(RendererInfo.IsInteractive ? "Search" : "Loading")
</button>
@code {
private string? _term;
private string? _result;
private void Search() => _result = $"Searched for {_term}";
}
Discussion