Why Blazor Auto Mode Finally Solves the SSR vs WASM Debate (.NET 10)

Read this article on Medium.

The Blazor Identity Crisis

If you’ve built anything serious with Blazor, you’ve hit the same wall:

Should this be Server, WASM, SSR… or Auto?

And if we’re being honest, most of us didn’t choose — we compromised.

  • Server for fast startup… but sluggish interactions
    We took the instant first paint, knowing every click quietly paid the tax of a network round-trip.
  • WASM for rich UX… but painful load times
    We accepted a heavy upfront download in exchange for smooth, near-native interactions once the app finally came alive.
  • SSR for SEO… but limited interactivity
    We shipped fast, crawlable pages while postponing real interactivity until the app could be hydrated or enhanced.

For years, the Blazor community debated hosting models as if one had to win.

Blazor Auto Mode ends that debate,
not by picking a side, but by changing the rules.

With .NET 10Auto Mode finally delivers on what Blazor promised from the beginning: one app, one codebase, adaptive execution.

Why the SSR vs WASM Debate Was Always the Wrong Question

The original debate was built on a flawed assumption:

That an application must commit to a single execution model for its entire lifetime.

But real applications don’t work that way.

SSR excels at:

  • Fast first paint
  • SEO and crawlability
  • Authentication and onboarding flows
  • Low-friction entry points

WASM excels at:

  • Rich, highly interactive UI
  • Long-lived client-side state
  • Near-zero latency after load
  • Horizontally scalable user experiences

Each model optimizes a different phase of the user journey.

The problem was never SSR versus WASM.
The problem was forcing one to do everything.

What Blazor Auto Mode Actually Is (No Marketing)

Let’s strip away the buzzwords.

Blazor Auto Mode is adaptive execution.

It allows a single application to:

  • Start as SSR
  • Hydrate into WebAssembly
  • Continue running client-side without a reload

No duplicated apps.
No separate deployments.
No branching logic.

Just one component model that moves execution when it makes sense.

The Core Mechanism: Render Modes

Blazor Auto Mode is built on explicit render modes, not magic.

@rendermode InteractiveAuto

That single line tells Blazor to:

  • Render the component on the server for first paint
  • Transition it to WebAssembly when the runtime is ready
  • Preserve UI and state seamlessly during the handoff

For clarity, the other render modes still exist:

@rendermode InteractiveServer
@rendermode InteractiveWebAssembly
@rendermode Static

Render Mode: InteractiveServer

  • Execution: Runs on the server.
  • UI updates: Sent to the client over SignalR in real time.
  • Startup: Very fast first paint because only HTML is sent.
  • Interaction: Every user action goes over the network → can feel sluggish under high latency.
  • Use case: Good for fast-loading pages with low interactivity or when you want to centralize logic on the server.

Render Mode: InteractiveWebAssembly

  • Execution: Runs entirely in the browser via WASM.
  • UI updates: All client-side — no network round-trip needed for interactions.
  • Startup: Slower initial load because the .NET runtime and DLLs are downloaded.
  • Interaction: Fast and smooth — near-native speeds once loaded.
  • Use case: Good for rich, interactive apps where instant responsiveness matters.

RenderMode: Static

  • Execution: Fully static HTML.
  • UI updates: None — it’s just markup.
  • Startup: Instant (very small payload).
  • Interaction: No interactivity unless enhanced later with JS or hydration.
  • Use case: Great for purely content-focused pages, like marketing pages or SEO-focused content.

Auto Mode doesn’t replace these,
it orchestrates them.

How Auto Mode Works (Conceptual Flow)

It works by showing users a fully rendered page immediately, then quietly handing control to the browser for smooth, interactive performance.

Mental model:

[ Browser Request ]

[ Server-Side Render (HTML) ]

[ User Sees UI Immediately ]

[ WASM Runtime Downloads ]

[ Hydration Occurs ]

[ Execution Moves Client-Side ]
  • [Browser Request] — The user navigates to the app, and the browser asks the server for the page.
  • [Server-Side Render (HTML)] — The server renders the component as HTML and sends it immediately.
  • [User Sees UI Immediately] — The user sees a fully rendered page without waiting for downloads.
  • [WASM Runtime Downloads] — The browser downloads the WebAssembly runtime and app DLLs in the background.
  • [Hydration Occurs] — Blazor links the static HTML to the WASM runtime, connecting UI elements to client-side logic.
  • [Execution Moves Client-Side] — From this point, all interactions are handled in the browser; the server is no longer needed for UI updates.

The Key Insight

From the user’s perspective, there is no “mode switch.”

  • No reload
  • No flicker
  • No reset
  • No broken state

This isn’t a handoff — it’s a continuation.

This is progressive enhancement done right.

Code Example: Auto Mode in a Real Component

Counter Example (Simple, but Telling)

What happens:

  • First render: executed on the server
  • First interaction: still server-backed
  • After hydration: runs entirely in WASM
@rendermode InteractiveAuto

<h3>Counter</h3>
<p>Current count: @count</p>
<button @onclick="Increment">Click me</button>

@code {
private int count;
private void Increment()
{
count++;
}
}

Same code.
Different execution context.
Zero developer intervention.

Why Execution Switching Matters in Real Apps

This is where Auto Mode stops being “cool” and starts being important.

Before Auto:

  • SSR apps felt laggy after login
  • WASM apps felt slow before login
  • Teams split apps or duplicated logic

With Auto:

  • Login pages load instantly
  • Dashboards feel native
  • Forms respond immediately
  • Navigation latency disappears

The app evolves with the user’s intent.

Real-World Tradeoffs (The Honest Section)

Auto Mode is powerful — but not magical.

It shines when:

  • You build authenticated business apps
  • You use feature-based architecture
  • State is intentional and scoped
  • Components are thin

It struggles when:

  • Business logic lives in UI components
  • State is global and uncontrolled
  • Large JS interop dependencies exist
  • You ignore execution boundaries

Auto Mode amplifies architecture — good or bad.

What .NET 10 Changes (And Why It Matters)

.NET 10 makes Auto Mode production-ready, not just impressive.

Notable improvements:

  • More reliable hydration
  • Improved state persistence across render modes
  • Reduced WASM payload size
  • Clearer diagnostics for execution context
  • Better performance during transitions

This unlocks:

  • Vertical slice architecture
  • Feature-level state
  • Cleaner boundaries between UI and logic

A Better Mental Model for Blazor Developers

Stop thinking in terms of:
❌ Server vs WASM
❌ Hosting models
❌ Deployment targets

Start thinking in terms of:
✅ Entry experience
✅ Interaction phase
✅ Execution locality

Auto Mode lets you design for UX first — and let execution follow.

Vertical Slice Architecture + Blazor Auto Mode

Why it matters

Vertical slices are all about features, not layers. Each slice contains everything needed for a single feature:

  • Data models / DTOs
  • Commands Queries
  • Handlers / Services
  • UI components

Blazor Auto Mode complements this perfectly:

  • Each slice can decide where and when code executes
  • SSR for initial render, WASM for interactive features
  • Minimal coupling, maximal UX

Example: Feature Slice — “Todo List”

Directory Structure:

This directory structure keeps each feature self-contained, organizing commands, queries, handlers, and UI together for cleaner, maintainable, and scalable code.

Features/
└─ Todos/
├─ Commands/
│ └─ AddTodoCommand.cs
├─ Queries/
│ └─ GetTodosQuery.cs
├─ Handlers/
│ ├─ AddTodoHandler.cs
│ └─ GetTodosHandler.cs
└─ UI/
└─ TodoList.razor

TodoList.razor with Auto Mode

This component demonstrates a self-contained feature using Blazor Auto Mode: it renders server-side initially for fast load, then seamlessly hydrates to client-side WASM for interactive updates, while using MediatR to cleanly handle commands and queries.

@rendermode InteractiveAuto
@inject IMediator Mediator

<h3>Todo List</h3>

<input @bind="newTodo" placeholder="Add a task" />
<button @onclick="AddTodo">Add</button>

<ul>
@foreach (var item in todos)
{
<li>@item.Title</li>
}
</ul>

@code {
private string newTodo = string.Empty;
private List<TodoDto> todos = new();

protected override async Task OnInitializedAsync()
{
// Fetch initial list (SSR first)
todos = await Mediator.Send(new GetTodosQuery());
}

private async Task AddTodo()
{
if (!string.IsNullOrWhiteSpace(newTodo))
{
var added = await Mediator.Send(new AddTodoCommand { Title = newTodo });
todos.Add(added);
newTodo = string.Empty;
}
}
}

Key points:

  • @rendermode InteractiveAuto ensures:
  • Initial HTML is server-rendered
  • WASM hydration occurs for interactive Add button
  • IMediator connects to your vertical slice handlers
  • No special logic needed for mode switching — Auto Mode handles it

Backend Slice Example

AddTodoCommand.cs

This line defines a simple command in the vertical slice patternencapsulating the action of adding a todo item and its expected result, while keeping business logic separate from UI.

public record AddTodoCommand(string Title) : IRequest<TodoDto>;

AddTodoHandler.cs

This class handles the AddTodoCommand by encapsulating the data-access logic: it creates a new Todo entity, saves it to the database, and returns a DTO, keeping business logic separate from UI and maintaining a clean vertical slice.

public class AddTodoHandler : IRequestHandler<AddTodoCommand, TodoDto>
{
private readonly TodoDbContext _db;

public AddTodoHandler(TodoDbContext db) => _db = db;

public async Task<TodoDto> Handle(AddTodoCommand request, CancellationToken ct)
{
var entity = new Todo { Title = request.Title };
_db.Todos.Add(entity);
await _db.SaveChangesAsync(ct);
return new TodoDto { Id = entity.Id, Title = entity.Title };
}
}

GetTodosQuery.cs

This line defines a query in the vertical slice pattern that represents the action of retrieving all todo items, returning a list of DTOs while keeping data access logic separate from the UI.

public record GetTodosQuery() : IRequest<List<TodoDto>>;

GetTodosHandler.cs

This class handles the GetTodosQuery by retrieving all Todo entities from the database, projecting them into DTOs, and returning a list, keeping query logic separate from UI and following the vertical slice pattern.

public class GetTodosHandler : IRequestHandler<GetTodosQuery, List<TodoDto>>
{
private readonly TodoDbContext _db;

public GetTodosHandler(TodoDbContext db) => _db = db;

public async Task<List<TodoDto>> Handle(GetTodosQuery request, CancellationToken ct)
{
return await _db.Todos
.Select(t => new TodoDto { Id = t.Id, Title = t.Title })
.ToListAsync(ct);
}
}

The result: Each feature “just works” regardless of how it’s rendered or executed, the user experience is seamless, the code stays clean.

Final Takeaway: The Debate Is Over — The Strategy Isn’t

The SSR vs WASM debate lasted so long because it was the wrong fight.

The real question was always:
“Where should this code execute right now?”

Blazor Auto Mode answers that question — dynamically, intelligently, and invisibly.

With .NET 10Blazor didn’t choose sides.

It evolved.