Introduction

Blazor Server maintains a persistent SignalR connection (circuit) between the browser and server. When this connection drops (network interruption, server restart, load balancer timeout), the user sees a reconnection UI. If the circuit cannot be restored within the configured time, the page must be refreshed, losing all component state. This is a significant UX issue for production Blazor Server apps.

Symptoms

  • "Attempting to reconnect to the server" message persists indefinitely
  • "Reload" button appears after reconnection timeout
  • Connection drops every 30-60 seconds behind a load balancer
  • Circuit lost after server deployment
  • WebSocket connection closed in browser console

Example browser console output: `` [2026-04-09T10:15:00.000Z] Information: Normalizing '/_blazor' to 'https://myapp.com/_blazor'. [2026-04-09T10:15:30.000Z] Warning: Connection timeout. [2026-04-09T10:15:35.000Z] Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message from the server'.

Common Causes

  • Load balancer not configured for WebSocket support
  • Server restarted during deployment, killing all circuits
  • SignalR keepalive interval too short or too long
  • Reverse proxy (Nginx, HAProxy) not forwarding WebSocket headers
  • Circuit lifetime too short for the expected disconnection duration

Step-by-Step Fix

  1. 1.Configure reconnection behavior:
  2. 2.```razor
  3. 3.<!-- _Host.cshtml or App.razor -->
  4. 4.<component type="typeof(App)"
  5. 5.render-mode="ServerPrerendered"
  6. 6.param-Reconnect="@(new ReconnectOptions
  7. 7.{
  8. 8.MaxRetries = 10,
  9. 9.RetryIntervalMilliseconds = 2000
  10. 10.})" />
  11. 11.`
  12. 12.Customize the reconnection UI:
  13. 13.```razor
  14. 14.<!-- _Host.cshtml -->
  15. 15.<div id="reconnect-modal" style="display: none;">
  16. 16.<div class="reconnect-overlay">
  17. 17.<div class="reconnect-content">
  18. 18.<h3>Connection Lost</h3>
  19. 19.<p>Attempting to reconnect... <span id="reconnect-count">0</span></p>
  20. 20.<button id="manual-reconnect" onclick="Blazor.reconnect()">Reconnect Now</button>
  21. 21.<button id="reload-page" onclick="window.location.reload()">Reload Page</button>
  22. 22.</div>
  23. 23.</div>
  24. 24.</div>

<script src="_framework/blazor.server.js"></script> <script> Blazor.defaultReconnectionHandler._reconnectionCallback = () => { document.getElementById('reconnect-modal').style.display = 'block'; }; Blazor.defaultReconnectionHandler._reconnectionSucceededCallback = () => { document.getElementById('reconnect-modal').style.display = 'none'; }; </script> ```

  1. 1.Configure SignalR keepalive and timeouts:
  2. 2.```csharp
  3. 3.builder.Services.AddServerSideBlazor(options =>
  4. 4.{
  5. 5.options.DetailedErrors = builder.Environment.IsDevelopment();
  6. 6.options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(5);
  7. 7.options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
  8. 8.options.MaxBufferedUnacknowledgedReceiveUps = 10;
  9. 9.})
  10. 10..AddHubOptions(options =>
  11. 11.{
  12. 12.options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
  13. 13.options.KeepAliveInterval = TimeSpan.FromSeconds(15);
  14. 14.options.HandshakeTimeout = TimeSpan.FromSeconds(15);
  15. 15.options.MaximumReceiveMessageSize = 64 * 1024; // 64KB
  16. 16.options.StreamBufferCapacity = 10;
  17. 17.});
  18. 18.`
  19. 19.Configure load balancer for WebSockets:
  20. 20.```yaml
  21. 21.# Kubernetes Ingress
  22. 22.apiVersion: networking.k8s.io/v1
  23. 23.kind: Ingress
  24. 24.metadata:
  25. 25.annotations:
  26. 26.nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  27. 27.nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  28. 28.nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
  29. 29.nginx.ingress.kubernetes.io/configuration-snippet: |
  30. 30.proxy_set_header Upgrade $http_upgrade;
  31. 31.proxy_set_header Connection "upgrade";
  32. 32.`
nginx
# Nginx configuration
location /_blazor {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

Prevention

  • Configure load balancer with WebSocket support and long timeouts
  • Set KeepAliveInterval to 15 seconds (default) and ClientTimeoutInterval to 30-60 seconds
  • Use sticky sessions if running multiple Blazor Server instances
  • Implement circuit state persistence for critical user data
  • Add custom reconnection UI with manual retry options
  • Monitor SignalR connection metrics in Application Insights