CoderBlog
ASP.NET

Blazor United and the .NET 9 Renaissance: Writing Full-Stack Web Apps in 2026 Without Leaving C#

A working engineer's guide to the unified Blazor story in .NET 9 — what Blazor United actually changed, what stayed the same, where the framework wins in production, and where it still hurts.

For most of the last decade, the advice for "I want to build a web app" in the Microsoft stack was the same: write the front end in JavaScript, write the back end in C#, write the contracts twice, and accept the impedance mismatch as the cost of doing business. The JavaScript half matured. The C# half matured. The bridge between them mostly did not. Then, in 2024, .NET 8 quietly shipped a feature called Blazor United, and in 2025 .NET 9 turned it into the default. In 2026, for the first time in a long time, a C# developer can ship a serious full-stack web app without leaving the language they already know. This is what that actually looks like in production.

Cover image: Blazor United and the .NET 9 stack

Fig. 01 — A working schematic of the modern Blazor stack: one project, multiple render modes, one mental model.

What Blazor United Actually Is

Blazor United is not a new framework. It is the thing Blazor should have been in 2018. Before .NET 8, "Blazor" was three different frameworks that happened to share a name: Blazor Server (long-lived SignalR connection, server-side rendering), Blazor WebAssembly (downloaded runtime, in-browser execution), and Blazor Hybrid (a WebView wrapper inside .NET MAUI). Each had its own project template, its own hosting model, its own deployment story, and its own caveats. Picking one meant committing to a deployment posture on day one and rewriting significant glue if you ever wanted to mix modes.

Blazor United collapses all three into a single project template and a single component model. You write a component. You annotate it — implicitly or explicitly — with a render mode. The same <button @onclick="..."> code works whether the click is being processed in the browser, on the server, or in a WebView. The same routing, the same DI, the same state management, the same authentication. You can mix render modes on a per-page or per-component basis, and you can start one way and migrate to another without rewriting the components themselves.

The thing that took me a while to internalize is that Blazor United is not just a refactor. It removes a class of decisions that used to be load-bearing. "Should this page be SSR or client-side?" used to be a question I had to answer before writing the first line. Now it is a question I can defer until the page is shipping and traffic is real. For a team that has been burned by the wrong choice, that is the actual win.

The .NET 9 Performance Story

The reason Blazor United lands in 2026 and not 2024 is that .NET 9 made the default render mode — InteractiveServer with auto-rendering — fast enough to use without apology. .NET 9 brought a new server-side rendering mode called Static SSR that produces pure HTML on the server with no SignalR connection at all, plus a new hybrid mode that boots a Server circuit on first interaction and then decides whether to upgrade to WebAssembly based on what the client can support. The upgrade decision is automatic, and it happens once per session.

@page "/dashboard"
@rendermode InteractiveAuto

<h1>Your dashboard</h1>
<DashboardGrid Data="@_data" />

The numbers we have seen in production are roughly these: a Static SSR page in .NET 9 is competitive with Next.js or Astro on Time to First Byte and Time to Interactive, within the noise. An InteractiveServer page is faster than a comparable React app over a good network because there is no JS bundle to download and parse before the user can click. An InteractiveWebAssembly page is now under 2 MB of compressed payload for a real application, down from the 8-10 MB that WebAssembly apps shipped in 2022. Most of that improvement is the new AOT compiler and a smaller BCL.

What .NET 9 does not do is give you a free lunch on bad architecture. If you put your entire business logic in a single root component and re-render the world on every keystroke, you will have a slow app. The render model is no longer a constraint; the render discipline is. Treat InteractiveServer as a hint to the framework, not a permission to ignore component boundaries.

EF Core 10 in Practice

The other half of the "full stack without leaving C#" story is EF Core 10, which shipped alongside .NET 9. EF Core 10 has two improvements that change how I write data access code. The first is compiled models. You add a single line to your Program.cs:

options.UseDbContextFactory<MyContext>(opt => opt.UseNpgsql(connStr));

and the model that EF Core builds from your entities is generated at build time rather than on first query. In our benchmarks this cut cold-start time for the data layer by 40% on a model with 80 entities. For serverless workloads that was the difference between a 1.2-second cold start and a 700ms cold start. The second improvement is JSON column support that actually feels native, not bolted on. You can map a property to a JSON column, query inside the JSON, and project it back without writing a custom converter. This is the feature that finally makes PostgreSQL's jsonb (or SQL Server's json or SQLite's json1) feel like a first-class citizen instead of a workaround.

The thing I appreciate most is that EF Core 10 stopped pretending to be a magic database. The new AsSplitQuery() is the default for collections with more than three levels of nesting, and the warning messages tell you exactly what the trade-off is. If you want one big query, you opt in. If you want a split query, you get it. The framework no longer tries to guess.

When NOT to Use Blazor

I have shipped Blazor in production since the WebAssembly preview days, and I have a list of situations where I would not pick it again today. Sharing the list because it is more useful than another happy-path testimonial.

  • Heavy real-time collaboration. If you need 60fps multi-cursor editing of a single document, the framework is not your bottleneck, but the lack of mature shared-state libraries means you will end up building a lot of the editor infrastructure yourself. Reach for CRDTs (Yjs, Automerge) regardless of framework; if you do, React or Solid will integrate with them more cleanly.
  • Massive content sites with millions of pages. The static SSR story in Blazor is good but not as good as Astro or 11ty for sites that are 99% reading and 1% interaction. The framework tax is real, even when it is small.
  • Mobile-first design teams that live in Figma and Storybook. The Blazor component ecosystem is improving but it is not Figma-shaped. If your design team has built a 200-component library in Storybook, you will be swimming upstream.
  • Anything where the JavaScript ecosystem is 5x more productive than the C# ecosystem. WebGL, 3D, audio synthesis, generative art, browser-based ML — all of these have decade-deep JavaScript tooling and a thin .NET layer on top. You can wrap it, but you are wrapping.

For everything else — internal tools, line-of-business apps, B2B SaaS, developer platforms, content sites with a real interactive layer — Blazor United in 2026 is a serious choice. Not the only choice. Not the cheapest. But for a team that already knows C# and ASP.NET Core, it is the path of least resistance between an idea and a working product.

The Shape of What Comes Next

We are at the end of the "should we use a single-page app at all" debate. The answer in 2026 is "yes, for the parts that need to be one, and no for the parts that don't." Blazor United is one of the better implementations of that answer. The other contenders — Next.js with React Server Components, SolidStart, SvelteKit, LiveView in Elixir — all converge on a similar architecture for similar reasons. The era of "pick a framework and ride it" is over. The era of "pick a render mode per page and a framework that supports that mix" is here.

The engineers who will get the most out of this are the ones who can hold the whole stack in their head. Not because they need to write every line, but because they need to know which line is doing what. In .NET 9, that is the front-end component, the back-end endpoint, the EF Core query, and the SQL it compiles to. The whole stack, in one language, with one mental model. That is the renaissance. The rest is just typing.

Five Things That Will Bite You on the Way

Before you commit, here are the five migration snags we hit on real projects, with the fix that worked for us.

1. Static SSR does not mean static. A page marked as Static SSR is still re-executed on every request by default. If you want true prerendering, you have to opt in with <PersistRenderState Prerender="true" /> or by returning an IComponentRenderMode.Static from a PageTransition. The naming is confusing. Read the docs on prerendering twice.

2. CSS isolation has a sharp edge. Blazor's scoped CSS files (MyComponent.razor.css) compile to a hash that includes the project assembly name. If you ever rename your assembly — which happens more often than you would think during a rename of the root namespace — every component's hash changes and every CSS file becomes orphaned. The build will succeed. The page will look unstyled. You will spend an afternoon finding the cause.

3. InteractiveServer has a memory ceiling. A single server circuit holds the component tree, the state, and the SignalR buffers. We have measured a comfortable 2,000-3,000 concurrent circuits per server before the GC pauses start showing up. InteractiveWebAssembly does not have this problem. If you are building a public-facing site that needs to scale to tens of thousands of concurrent users, the answer is "most pages Static SSR, only the interactive parts InteractiveServer, and the heavy interactive stuff InteractiveWebAssembly with AOT." Blazor United supports this. It does not pick it for you.

4. Form validation gets weird across render modes. When a form is submitted under InteractiveServer, the validation runs server-side. When the same form is submitted under InteractiveWebAssembly, the validation runs in the browser. The behavior is correct in both cases, but the timing of when errors appear is different. Users notice. Test your forms under both modes before shipping.

5. The JavaScript interop story is better than it was, still not great. Calling JS from Blazor is now IJSRuntime.InvokeAsync. Calling C# from JS is [JSImport]. Both work. Neither feels native. If your app is more than 30% JavaScript interop, you are using the wrong framework.

The list is not a reason to avoid Blazor. It is a reason to write the first three pages in it before you write the first thirty. The framework rewards teams who use it on its terms and penalizes teams who try to make it something it is not. In 2026, on those terms, it is one of the best choices you can make on the Microsoft stack.

The renaissance is not the language. The renaissance is that you can hold the whole stack in your head without dropping any of it.

A small toolkit to get started, if you have not already: install the .NET 9 SDK, then dotnet new blazor to get the unified template. Add Microsoft.EntityFrameworkCore.Design and your provider (Npgsql.EntityFrameworkCore.PostgreSQL, Microsoft.EntityFrameworkCore.SqlServer, or Microsoft.EntityFrameworkCore.Sqlite). Add Microsoft.AspNetCore.Components.WebAssembly.Server if you want a real .NET backend. Run dotnet ef migrations add Initial and dotnet run. You will have a working full-stack app in under five minutes. Spend a Saturday doing the same CRUD page three different ways — server, webassembly, auto — and watch the network tab and the memory profile in DevTools. The surprise is the data, the timing, and the bundle size all at once. That is where the new shape of the work is becoming visible to you in practice, today.

Winson Yau

Engineer, writer, and founder of CoderBlog. Building tools and writing about the craft of software from Hong Kong.

Comments

Discuss the article below. Markdown is supported. Sign in with email or GitHub to leave a comment.