Render Modes — Server, WASM, Auto
Key Points
- Blazor (.NET 8+) unified Server + WASM under render modes. One project; per-component or per-page choice.
- Static SSR — fastest first paint; no interactivity. Good for content pages.
- Interactive Server — UI events go over SignalR to server; tiny WASM payload; needs persistent connection.
- Interactive WebAssembly — full SPA in browser; slower initial load (download .NET runtime); offline capable.
- Interactive Auto — starts as Server (instant), upgrades to WASM after download. Best UX; setup complexity.
- Default: Static SSR for content; Auto for app-like pages; pick per page based on tradeoffs.
Concepts (deep dive)
The four modes
┌─────────────────┬──────────────┬────────────┬──────────────┐
│ Mode │ First load │ Latency │ Online req'd │
├─────────────────┼──────────────┼────────────┼──────────────┤
│ Static SSR │ Fastest │ N/A (no UI)│ No │
│ Interactive Srv │ Fast │ Network RTT│ Yes (SignalR)│
│ Interactive WASM│ Slow │ ~ms │ No │
│ Interactive Auto│ Fast → fast │ Network→ms │ Initially Yes│
└─────────────────┴──────────────┴────────────┴──────────────┘
Setting render mode
@* Per-component *@
@rendermode InteractiveServer
@rendermode InteractiveWebAssembly
@rendermode InteractiveAuto
@* Static SSR is default — no @rendermode *@
@* Per-page *@
@page "/dashboard"
@rendermode @(new InteractiveServerRenderMode(prerender: false))
<Counter />
In Program.cs:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();
Static SSR
No interactivity (no @onclick handlers fire). Server renders HTML; browser displays. Forms submit via traditional HTTP POST.
Use for: content pages, landing pages, server-rendered forms.
Interactive Server
@page "/counter"
@rendermode InteractiveServer
<button @onclick="() => count++">Click @count</button>
@code { int count; }
UI events serialize to server over SignalR (WebSocket). Server runs handler; computes diff; sends back. Tiny client payload, server holds state.
Pros: - Tiny initial download. - Full .NET runtime, libraries. - Fast cold start.
Cons: - Per-user circuit (server memory). - Needs persistent connection. - Network RTT per interaction. - Doesn't scale to massive concurrent (need backplane).
Interactive WebAssembly
@page "/calculator"
@rendermode InteractiveWebAssembly
<button @onclick="() => result = a + b">+</button>
@code { int a, b, result; }
.NET runtime downloads to browser; runs in WASM. Standalone after load.
Pros: - Local execution; no server roundtrip. - Offline capable (PWA). - No server state.
Cons: - Initial download large (~5-10 MB compressed for runtime). - Slower cold start. - Fewer .NET features (no full BCL). - Browser sandbox limits.
Interactive Auto
First visit: Interactive Server (fast). Background: WASM downloads. Subsequent visits / after download: WASM.
Best UX — fast first load + local execution after.
Trickier: - Component runs on both server (initially) and WASM (later). - State must be transferable. - Both sides must work — every dep must work in browser.
Prerendering
Server renders HTML before sending. Improves SEO + first paint.
Pitfall: component runs twice — once during prerender, once when interactive. State must survive the boundary (PersistentComponentState).
Choosing
Content / SEO / static / minimal interactivity → Static SSR
LOB app, internal, online-required, server-resource-heavy → Interactive Server
Public app, offline support needed, big payload acceptable → Interactive WASM
Best UX (fast first load + local after) → Auto
Mixed modes in one app
@page "/"
@* Static SSR *@
<h1>Home</h1>
<NavLink href="/dashboard">Dashboard</NavLink>
@* Dashboard.razor uses Interactive Server *@
Per-page or per-component decisions.
State management
| Mode | State |
|---|---|
| Static SSR | Per-request; URL/form |
| Interactive Server | Per-circuit (per user/tab) on server |
| Interactive WASM | In-memory in browser tab |
| Auto | Both — must transfer |
For cross-render-mode state, use PersistentComponentState:
[Inject] PersistentComponentState State { get; set; } = default!;
protected override Task OnInitializedAsync()
{
if (State.TryTakeFromJson<MyData>("data", out var d) && d is not null)
_data = d;
else _data = await LoadAsync();
State.RegisterOnPersisting(() =>
{
State.PersistAsJson("data", _data);
return Task.CompletedTask;
});
return Task.CompletedTask;
}
State serialized into HTML; client picks up after WASM boots.
Authentication
AuthenticationState flows through both modes. Cookie auth typical for Server; PKCE OIDC for standalone WASM. See Auth in Blazor.
SignalR backplane (Interactive Server scale)
Multi-instance Interactive Server needs backplane (Redis or Azure SignalR). Otherwise, sticky LB.
WASM payload optimization
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<BlazorWebAssemblyEnableLinking>true</BlazorWebAssemblyEnableLinking>
</PropertyGroup>
Trimming removes unused code; AOT for fast execution but bigger size.
Common pitfalls
- Mixing prerender + interactive WASM — component runs twice; state issues.
- Heavy SQL calls in Interactive Server
OnInitializedAsync— runs per circuit. - Calling browser APIs in prerender —
IJSRuntimenot available. - Auto without WASM-compatible deps — components fail when upgraded.
Code: correct vs wrong
❌ Wrong: per-component without thought
Everywhere — server-resource heavy.
✅ Correct: Static SSR by default
❌ Wrong: WASM lib calling System.IO
✅ Correct: API call
Design patterns for this topic
Pattern 1 — "Static SSR by default; interactive opt-in"
- Intent: minimum complexity per page.
Pattern 2 — "Auto for best UX"
- Intent: fast first + local after.
Pattern 3 — "PersistentComponentState"
- Intent: state across mode boundary.
Pattern 4 — "SignalR backplane for scale"
- Intent: multi-instance Server.
Pattern 5 — "Trim + compress WASM"
- Intent: smaller download.
Pros & cons / trade-offs
| Mode | Pros | Cons |
|---|---|---|
| Static SSR | Fastest; SEO | No interactivity |
| Interactive Server | Tiny payload | Needs connection; server state |
| Interactive WASM | Local; offline | Big download |
| Auto | Best UX | Setup complexity |
When to use / when to avoid
- Use Static SSR for content.
- Use Interactive Server for internal apps.
- Use WASM for offline / no server state.
- Use Auto for best UX.
- Avoid mixing without clear strategy.
Interview Q&A
Q1. Four render modes? Static SSR, Interactive Server, Interactive WebAssembly, Interactive Auto.
Q2. Static SSR — interactivity? None. Forms via HTTP POST.
Q3. Interactive Server — how events flow? Over SignalR/WebSocket to server; server computes diff; sends back.
Q4. Interactive WASM cold start? Slow — runtime download (~5-10 MB compressed). Cached after.
Q5. Auto — what's the trick? Server initially (fast); WASM downloads in bg; future loads use WASM.
Q6. Prerender pitfalls? Component runs twice (prerender + interactive). State must survive — PersistentComponentState.
Q7. State across modes? PersistentComponentState transfers state from server prerender to WASM.
Q8. Multi-instance Interactive Server scaling? Sticky LB or SignalR backplane.
Q9. WASM trimming? PublishTrimmed true. Removes unused code.
Q10. When choose Auto over Server-only? When you want offline / lower server load AFTER initial load + can tolerate WASM download.
Q11. Per-component vs per-page mode? Both supported. Per-page common.
Q12. Static SSR alternatives? Razor Pages / MVC. Static SSR Blazor reuses component model.
Gotchas / common mistakes
- ⚠️ Default Interactive Server everywhere — server load.
- ⚠️ Browser-only APIs in prerender.
- ⚠️ No PersistentComponentState — data refetched.
- ⚠️ WASM-incompatible libs in Auto.
- ⚠️ No backplane in multi-instance Server.