Why Blazor Server scales differently
Every connected user holds an open SignalR WebSocket circuit on your server. That circuit stores UI state in memory. 1,000 users = 1,000 server-side state bags. MVC and Razor Pages don't have this problem — they're stateless between requests.
This is Blazor Server's superpower and its load ceiling. Handled right, it's fine at scale. Handled wrong, you run out of RAM at 300 users.
Settings that actually matter
1. Disable detailed errors in production
builder.Services.AddServerSideBlazor(opts =>
{
opts.DetailedErrors = builder.Environment.IsDevelopment();
});
DetailedErrors = true leaks stack traces to clients AND keeps references alive for debugging. Prod should be false.
2. Cap circuit memory
builder.Services.AddServerSideBlazor(opts =>
{
opts.DisconnectedCircuitMaxRetained = 50; // default: 100
opts.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(2);
opts.MaxBufferedUnacknowledgedRenderBatches = 4; // default: 10
});
Lower the buffers and retention. Users on flaky connections won't notice. Your RAM will.
3. Compression
builder.Services.AddResponseCompression(o =>
{
o.MimeTypes = ResponseCompressionDefaults.MimeTypes
.Concat(new[] { "application/octet-stream" });
});
app.UseResponseCompression();
SignalR payloads are compressible. Compression over WebSockets saves ~60% bandwidth.
Load-balancing: sticky sessions are mandatory
Blazor Server circuits are stateful. If you round-robin load-balance, users will drop mid-interaction. Configure session affinity (sticky sessions) in your load balancer — nginx uses ip_hash, Azure uses ARR Affinity cookie, Kubernetes ingress needs sessionAffinity: ClientIP.
Measure before optimising
Use dotnet-counters monitor Microsoft.AspNetCore.Components.Server to see live circuit counts. The counter connected-circuits is the magic number — each is roughly 100–500 KB depending on your component tree.
Target memory per circuit: under 200 KB. Above that, audit for captured large objects in component fields.
When Blazor Server is not the answer
If you expect 5,000+ concurrent users on a single box, switch to Blazor WebAssembly for those pages. Interactive-on-demand per-component is supported in .NET 8+. You don't have to pick one model for the whole app.