Blazor ServerAugust 2026 · 7 min read

Blazor Server Architecture: How SignalR, DI, and Razor Components Fit Together

TL;DR

Blazor Server runs all C# component code on the server and keeps the browser in sync over a persistent SignalR circuit. Each circuit gets its own dependency injection scope, so scoped services become per-user state, and the render tree sends only DOM diffs to the browser instead of whole pages.

Blazor Server Architecture is a .NET hosting model that runs all C# component code on the server and keeps the browser in sync through a persistent SignalR connection. When a user clicks a button, the event travels across that connection, the Razor component re-runs its logic, and only the minimal UI diff is sent back. Once you understand how the circuit, dependency injection, and the render tree fit together, most Blazor Server problems become predictable and easy to solve.

This article walks through each moving part in order: the SignalR circuit that creates the illusion of a desktop application, the scoped services that give each circuit its own state, the render tree that computes what changed, and the component lifecycles that hook into every phase.

How the Blazor Server Architecture Uses SignalR Circuits

When a browser first loads a Blazor Server page, the server opens a SignalR circuit — a pair of logical connections that stay alive for the entire session. The circuit is the boundary of your application's state: every interactive component instance created inside that circuit lives on the server, in memory, for as long as the user keeps the tab open.

Each circuit is isolated from every other circuit. Two users on the same page never share component instances, even though they share the same process. That isolation is exactly why dependency injection scopes matter, and it is the reason the architecture scales horizontally without session-affinity headaches: a circuit is pinned to one server, but a healthy application routes new circuits wherever capacity exists.

Razor Components and the Render Tree

A Razor component is a C# class with a markup template. When a component renders, it produces a render tree — a lightweight description of the DOM it wants. The renderer diffs the new render tree against the previous one and serializes only the changes, not the whole page.

This diffing is what makes Blazor Server feel instant even though all logic runs on the server. The browser never re-renders the entire document; it just applies the small patches that SignalR delivers. Components are the unit of reuse: a grid, a form, a picker is a component you compose, and each one contributes its own subtree to the render tree.

Dependency Injection Scoping Inside Blazor Server Architecture

Dependency injection in Blazor Server follows the lifetime rules you already know: Transient services are created on every resolution, Singleton services live for the whole application, and Scoped services are created once per scope. The twist is what a scope means here.

In Blazor Server, a scope is created for each circuit, not for each HTTP request. A Scoped service registered with AddScoped is resolved once per circuit and shared by every component in that circuit. This is the single most important DI fact to remember when you design for this architecture. Here is how the registration and usage look in a real .NET 10 application:

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddScoped<UserSessionState>();
builder.Services.AddScoped<AppDbContext>();
@page "/customers"
@inject UserSessionState Session
@inject AppDbContext Db

<ul>
    @foreach (var c in Customers)
    {
        <li>@c.Name</li>
    }
</ul>

@code {
    private List<Customer> Customers = new();

    protected override async Task OnInitializedAsync()
    {
        Customers = await Db.Customers
            .Where(c => c.TenantId == Session.TenantId)
            .OrderBy(c => c.Name)
            .ToListAsync();
    }
}

The UserSessionState and AppDbContext instances are created the first time a component asks for them inside a circuit, and they are disposed together when the circuit ends. EF Core's DbContext is designed for exactly this lifetime: short-lived per unit of work, shared within the operation, and disposed cleanly when the scope goes away.

Event Dispatch and Component Lifecycles

Every event in Blazor Server follows the same pipeline. The browser sends the event over SignalR, the renderer finds the owning component, calls the event handler, marks the component as dirty, and re-renders it. If the handler calls StateHasChanged, the render happens immediately after the handler returns.

Along the way, the framework invokes lifecycle methods at fixed points:

  • OnInitializedAsync — runs once when the component is first added to the circuit
  • OnParametersSet — runs after parameters are set, including from parent re-renders
  • OnAfterRenderAsync — runs after the DOM is updated in the browser
  • IDisposable / IAsyncDisposable — clean up timers, subscriptions, and unmanaged resources

Because the component lives server-side, you must dispose everything you create. A timer that ticks forever, an event subscription that is never removed, or an HttpClient that is never released will silently leak memory for the lifetime of the circuit. The container helps by disposing scoped services, but components must dispose what they own.

Blazor Server vs WebAssembly in One Glance

Blazor WebAssembly runs the .NET runtime in the browser; Blazor Server keeps everything on the server. Blazor Server wins on startup time, bundle size, and security because the business logic never leaves the server. Indotalent ships every product as Blazor Server for exactly those reasons — you get full C# access to the database and to protected services without exposing a single line of it.

Choose Blazor Server when you want immediate interactivity, a small first load, and server-controlled state. Choose WebAssembly when you need true offline support or want to move rendering off your servers entirely. For enterprise business applications, Blazor Server is almost always the right call — which is why the entire Indotalent product line runs on it.

Key Takeaways

  • Blazor Server Architecture keeps all C# code on the server and syncs the UI over SignalR
  • A circuit is a persistent, isolated session — each one gets its own dependency injection scope
  • Scoped services are created once per circuit and shared by every component in it
  • The render tree makes updates cheap by sending only diffs to the browser
  • Lifecycle methods and IDisposable are mandatory hygiene for long-lived server components

FAQ

Is a Blazor Server circuit the same as an HTTP request?

No. A circuit is a long-lived connection that can outlive dozens of HTTP calls. It lasts until the user closes the tab, navigates away from the app, or the server drops the connection.

Why are scoped services per circuit instead of per request?

Because Blazor Server is not request-driven. The circuit is the unit of state, so scoped services match the circuit lifetime. That is how a DbContext or a user session can safely span multiple UI interactions.

Does every user get their own render tree?

Yes. Each circuit has its own renderer and its own component instances. Nothing is shared between circuits unless you explicitly use a Singleton service, and singletons must stay stateless or tenant-keyed.

Can Blazor Server work over a plain HTTP connection?

No. Interactive Blazor Server requires the SignalR transport. If SignalR cannot connect, the app can still render statically, but it will not be interactive until the connection is restored.

Ready to explore a real Blazor Server codebase?

Every Indotalent product is a complete .NET 10 application built with Blazor Server, Vertical Slice Architecture, and MudBlazor. Complete .NET 10 source code — $21 each.

Explore Products