Blazor lets you write a UI once, in C# and Razor, and then run it in three genuinely different places: on the server over a live connection, inside the browser as WebAssembly, or inside a native app on a phone or a desktop. Most comparisons stop at a feature grid. This one has a better source. The same clinic app, built in public across three seasons, has shipped on Blazor Server and on Blazor Hybrid — and the site you're reading right now is the fourth flavor, static SSR. Almost every claim below has a repository behind it — and the one column that doesn't says so.
The short answer: choose by where your C# has to run. If the page is content, run nothing in the browser — static SSR, which is what coder000.com does. If the app is a connected tool that needs live data and you control the server, Blazor Server is the cheapest road to interactivity. If it must work offline, or scale as static files with no server in the loop, WebAssembly (or Interactive Auto). And if it needs the device — haptics, a camera, push notifications, secure storage — Blazor Hybrid inside .NET MAUI, which is what ClinicLive's companion app is.
Four ways to run Blazor, in one paragraph each
Static SSR renders a component to plain HTML on the server, once per
request, and hangs up. No circuit, no runtime download, no event handlers — just documents.
Interactive Server keeps your C# on the server and opens a SignalR
connection (the circuit) to each browser tab; every click travels to the server and
a tiny diff travels back. Interactive WebAssembly compiles your C# to run
inside the browser; after a bigger first download, clicks are handled locally and the app can
work offline. Interactive Auto starts a component in Server mode and swaps to
WebAssembly once it has downloaded. And Blazor Hybrid hosts the same
components in a BlazorWebView inside a .NET MAUI (or WPF, or Windows Forms)
app: the HTML renders in the platform's WebView, but the C# runs natively on the device —
no circuit, no WebAssembly. If the render modes themselves are new to you, the Blazor
series' Part 12 on render modes
explains each one from the ground up; this post assumes that and gets on with deciding.
The decision table
| Static SSR | Interactive Server | WebAssembly (and Auto) | MAUI Blazor Hybrid | |
|---|---|---|---|---|
| Where C# runs | Server, once per request | Server, per circuit | Browser | Natively on the device |
| Interaction latency | Full page or enhanced form round trip | One network hop per event | Local | Local |
| Offline | No | No — no connection, no events | Yes, as a PWA | Yes, plus local storage APIs |
| Hosting and scale | Any web server; stateless; cheapest | Memory and a WebSocket per user; sticky sessions or a SignalR service to scale out | Static files on a CDN; an API scales separately | No UI hosting; a store or sideload, plus a backend API |
| SEO and first paint | Best — plain HTML | Good — prerendered HTML, small script | Prerendered in a Blazor Web App; an empty shell if standalone | Not applicable |
| Device access | None | Browser APIs via JS interop, over the circuit | Browser APIs via JS interop, locally | Full native APIs from C# |
| Security surface | Smallest | Code and secrets stay on the server; each circuit costs memory | All code is public; needs an authorized API | App is an untrusted client; needs an authorized API and secure storage |
| Debugging | Ordinary ASP.NET Core | Best — breakpoints, one process | Browser dev tools bridged to the IDE | WebView dev tools for the UI, native tools for the edges |
| Wins when | Content, docs, marketing, blogs | Internal tools, dashboards, real-time apps you host | Offline apps, static hosting, no server budget | Anything that needs the phone or the desktop |
One honest note on that grid: WebAssembly is the one column this site's projects have not shipped. Its row comes from Blazor's documented behavior and my own smaller uses of it, not from a season with a build log. The other three columns have receipts, and the rest of this post is those receipts.
Latency and the interactivity model
Blazor Server's defining property is that every UI event is a network round trip. On a
wired office network that's invisible; on a phone in a car park it's the difference between
a button that feels instant and one that feels sticky. ClinicLive chose it anyway, and
season one's Part 5 gives the reason:
a waiting-room board and a staff chat need real-time updates, and Blazor Server is
already real-time under the paint. The whole hosting model is two lines of
Program.cs, plus one hub for fan-out beyond a single circuit:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
/* ... */
builder.Services.AddSignalR();
/* ... */
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.MapHub<ClinicLive.Hubs.QueueHub>("/hubs/queue");
WebAssembly and Hybrid flip that: the event is handled where the user is. The difference between those two is what "where the user is" means. In WebAssembly it's a browser sandbox running a .NET runtime; in Hybrid it's the real .NET runtime on the device, so a click handler can call the vibration motor directly. Static SSR has no client-side event model at all — enhanced navigation and enhanced forms make it feel smoother than a 2005 web page, but a counter button is not on the menu, and that is a feature, not a limitation, for a site made of articles.
Offline
Only two columns survive a lost connection. A WebAssembly app installed as a PWA keeps running from its cached files; a Hybrid app is an app, and it keeps its screens, its preferences and its secure storage whatever the network is doing. Season three's Part 9 is the worked example: a connectivity interface, a two-tier store (Preferences for the harmless, SecureStorage for the confirmation code) and a cached visit that renders before the API answers. Blazor Server does the opposite — no connection means no events, and the built-in reconnect UI exists precisely because that moment is so visible. If "works in a basement" is on your list, the decision is already made.
Hosting cost and scale: circuits versus static files
This is the row people underestimate. A Blazor Server user is a circuit: component state held in server memory, plus one WebSocket kept open for as long as the tab lives. A thousand idle tabs are a thousand circuits. Scaling out means every reconnect has to land on the server that holds that state, so you need sticky sessions at the load balancer or a hosted SignalR service in front. And your reverse proxy has to forward the WebSocket upgrade, or nothing errors and everything limps. Season one's Part 12 paid that tuition on a plain VPS; these are the nginx lines that mattered:
proxy_http_version 1.1; # SIGNALR: WebSockets need HTTP/1.1
proxy_set_header Upgrade $http_upgrade; # SIGNALR: pass the upgrade through
proxy_set_header Connection $http_connection; # SIGNALR: keep the tunnel open
proxy_read_timeout 100s; # circuits are long-lived; default 60s drops them
A WebAssembly app is the other extreme: after publish it is a folder of static files. A CDN can serve it to the planet for pennies, and the only thing that needs a server is whatever API it calls — which scales like any API, separately from the UI. Static SSR sits near WebAssembly on cost (stateless, cache-friendly, any small server) while keeping the rendering on your side. A Hybrid app has no UI hosting bill at all; what it has instead is a distribution problem (a store listing or a signed sideload) and a CI matrix — season three ended with three build jobs where season one had one.
SEO and first paint
coder000.com is static SSR because it is a library of articles, and a crawler wants HTML,
not a promise of HTML. The site's Program.cs says so in its first comment,
and there is no blazor.web.js anywhere in its App.razor:
// Static server-side rendering only: every page ships as plain HTML,
// which keeps the site fast and fully crawlable.
builder.Services.AddRazorComponents();
/* ... */
app.MapRazorComponents<App>();
Interactive Server is nearly as good for search: components are prerendered to HTML on the server, and the client script that then wires up the circuit is small. WebAssembly's story depends on how you host it. Inside a Blazor Web App, WebAssembly components are prerendered too, so crawlers and first paint both see content; a standalone WebAssembly app sends an empty shell and a loading spinner until the runtime arrives, which is a bad first impression for a marketing page and irrelevant for a tool behind a login. Hybrid apps have no crawler to please.
Device access
Static SSR has none. Server and WebAssembly get whatever the browser offers, through JS interop — the difference being that in Server mode the call rides the circuit to one visitor's browser, which is why the Pocket web host registers those services scoped rather than singleton:
// Scoped, not singleton: these talk to ONE visitor's browser through that
// visitor's circuit (IJSRuntime is per circuit).
builder.Services.AddScoped<IHaptics, Haptics>();
builder.Services.AddScoped<INotifier, Notifier>();
builder.Services.AddScoped<ILocator, Locator>();
Hybrid is where the column changes color. The MAUI host answers the same interfaces with
real device APIs — nine of them over the season: platform info, lifecycle, haptics,
notifications, push registration, location, the camera scanner, connectivity and storage.
Season three's Part 2 explains
the capability-interface pattern that makes one set of screens run on all three hosts, and
Part 1 makes the case for it. The
native host is one XAML element wrapped around your existing Routes:
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents>
<RootComponent Selector="#app" ComponentType="{x:Type shared:Routes}" />
</BlazorWebView.RootComponents>
</BlazorWebView>
The cost of that column: the WebView is exactly that, a web view, and
the OS owns its edges. Season three's first Android screenshot had the header hidden
under the status bar because env(safe-area-inset-*) reads zero inside a
WebView — the fix was native, not CSS. Windows toast notifications never displayed all
season because an unpackaged app has no identity. Device access is real, and so are the
device's rules.
Security surface
Blazor Server has the most comfortable story: your code, your connection strings and your business rules never leave the server, and the browser only ever sees rendered HTML and diffs. Its cost is that every visitor holds server memory, so a page that does expensive work per circuit is a denial-of-service invitation — validate, throttle and keep circuits cheap. WebAssembly is the reverse: every assembly you ship is downloadable and decompilable, so a "secret" in a WebAssembly app is a published secret, and every authorization decision must be re-made by the API. Hybrid is WebAssembly's cousin here: the app runs on hardware the user owns. ClinicLive's API treats the phone as an untrusted client — the six-character confirmation code is the whole credential, checked on the server (Part 3) — and the app keeps that code in the platform's secure storage, not in a preferences file (Part 9).
Debugging
Server mode is the easiest of the four to debug, and it isn't close: one process, ordinary
breakpoints, exceptions with full stacks in your own console. WebAssembly debugging works
through the browser's dev-tools protocol bridged into Visual Studio or VS Code; it is fine,
and (my opinion) noticeably slower and occasionally moodier than debugging server code.
Hybrid splits the job in two. The Blazor half gets browser dev tools attached to the WebView
— AddBlazorWebViewDeveloperTools() in debug builds — while the native half
needs native tools: adb logcat, an emulator, a screenshot harness. Season
three's most instructive bugs — an ambiguous View type, a package-visibility
rule, a stopped-state push — were all found on the native side, and none of them show up in
a Razor file.
When each one wins
- Static SSR — anything whose job is to be read: docs, blogs, marketing, product pages, this site. Add an interactive island only where a page genuinely needs one.
- Interactive Server — line-of-business tools, dashboards, admin screens, and real-time apps where you already run the server: ClinicLive's kiosk, board and staff pages.
- WebAssembly, or Auto — apps that must survive offline, tools you want to host as static files, or products where the server budget is zero and the API is somebody else's.
- MAUI Blazor Hybrid — a companion to an existing Blazor product that needs the device: ClinicLive Pocket's push, haptics, camera, GPS and offline, sharing every screen with a web host.
Honestly, it depends
Three things complicate the tidy table. First, you don't pick one for the whole app. Render modes apply per component, so a Blazor Web App can be static SSR for its public pages, Interactive Server for its dashboard and WebAssembly for one offline widget. Interactive Auto exists precisely because the "Server or WebAssembly" fight is often a false choice.
Second, the Hybrid decision is really two decisions: Blazor Hybrid versus the other ways to build a mobile app, and then MAUI versus its rivals. Season three's retro asked whether Blazor Hybrid was the right bet and answered yes for that app — screens that are forms, cards and one big number — and no for an app whose product is the camera or gesture-heavy motion, where the WebView is the wrong stage. The framework half of that question gets its own post: .NET MAUI vs Flutter vs React Native for a .NET team.
Third, the hosting model is not the biggest decision you'll make. The database under all of this matters more to the bill and the migration story than the render mode does, and it's the same decision whichever column you choose — see PostgreSQL vs SQL Server for .NET developers.
If you only remember one thing: ask where the C# has to run. Nowhere in the browser — static SSR. On your server, with a live line to each user — Interactive Server. In the browser, with no server in the loop — WebAssembly. On the device, talking to the hardware — Hybrid. Every row in the table follows from that one answer.
Frequently asked
- Is Blazor Server or Blazor WebAssembly better?
- Neither is better in general. Blazor Server is the cheaper path to interactivity when you host the server and users are online, because the C# stays on the server and the download is tiny. WebAssembly wins when the app must work offline or be hosted as static files with no server in the loop. In a Blazor Web App you can mix both per component, and Interactive Auto starts in Server mode and switches to WebAssembly once it has downloaded.
- Can a Blazor Hybrid app share code with a Blazor website?
- Yes. Put the components in a Razor class library and reference it from both the .NET MAUI app and the web project; the maui-blazor-web template scaffolds exactly that. ClinicLive Pocket shares every screen between an Android app, a Windows app and a Blazor Server web host, with device features behind small interfaces that each host implements honestly.
- Is Blazor static server-side rendering good for SEO?
- Yes. Static SSR sends complete HTML for every page with no client-side runtime, so crawlers and readers see the content immediately. coder000.com runs on Blazor static SSR for exactly that reason. Interactive Server pages are also prerendered to HTML; a standalone WebAssembly app is the one flavor that serves an empty shell until the runtime loads.
The evidence for this post is public. Season one's From Prompt to Production builds the Blazor Server clinic; season three's From Prompt to Pocket puts the same screens in a MAUI app and a browser host; and the Blazor series' finale is the gentle introduction to all four render modes if you want the fundamentals before the decision.