blazor ssr 在 .net 8 中是动态服务端渲染,非静态生成;需配置 addserversideblazor()、render-mode="server"、mapblazorhub() 及 mapfallbacktopage;组件会构造两次,应通过 isconnected 区分阶段并避免非幂等操作。

Blazor SSR 在 .NET 8 中不是“静态服务器渲染”
Blazor 在 .NET 8 中的 Server 渲染模式(即 RenderMode.Server)是**动态服务端渲染**,不是静态生成。它每次请求都实时执行组件逻辑、调用生命周期方法、访问服务(如 IDbContextFactory 或 IHttpClientFactory),并返回带 SignalR 连接的交互式 HTML。所谓“静态”,仅指 HTML 初始内容不依赖客户端 JS 执行——但背后仍是活跃的服务器端会话和实时连接。
如果你需要真正无状态、可缓存、无 SignalR 依赖的 HTML 输出,RenderMode.Static 是唯一选项,但它完全禁用交互性(所有事件处理器被忽略,@onclick 不生效)。
.NET 8 中启用 Blazor SSR 的关键配置项
要在 .NET 8 中启用真正的服务端渲染(含交互性),必须显式设置 RenderMode 并启用相关中间件,不能只靠模板默认行为:
-
Program.cs中需调用builder.Services.AddServerSideBlazor()(不是AddRazorComponents()) - 页面或布局中使用
<component type="typeof(App)" render-mode="Server"></component>,且该组件必须继承自ComponentBase - 必须注册
Hub端点:app.MapBlazorHub()—— 缺少这行会导致页面加载后立即断连、事件无响应 -
MapFallbackToPage("/_Host")必须存在,且/_Host页面中render-mode值与组件一致(常见错误:页面写Server,但组件漏设或设为Static)
为什么 RenderMode.Server 会触发两次组件构造?
这是 .NET 8 SSR 的典型行为:首次响应由服务器同步渲染(生成初始 HTML + 注入 __blazor 启动脚本),随后浏览器立即发起 SignalR 连接,并在连接建立后**再次实例化同一组件**(执行 OnInitializedAsync 等),用于接管后续交互。这导致:
- 数据库查询、HTTP 调用等副作用可能被执行两次(除非加缓存或条件判断)
-
NavigationManager.Uri在首次渲染时不可靠(尚未注入),第二次才可用 - 依赖
HttpContext的逻辑在第二次渲染时会失败(因为 SignalR 连接不携带原始 HTTP 上下文) - 若组件有
@inject HttpContext HttpContext,会在 SignalR 阶段抛出InvalidOperationException: HttpContext is not available
如何让服务端渲染更可控、避免常见崩溃
关键不是“让它工作”,而是控制它何时执行什么逻辑:
- 用
IsConnected区分阶段:if (circuit is not null && circuit.IsConnected)可判断是否处于 SignalR 接管后的交互态(需注入CircuitHandler或检查NavigationManager状态) - 避免在
OnInitializedAsync中做非幂等操作;把数据加载移到OnParametersSetAsync并加if (!loaded) { ... }守卫 - 不要在组件中直接
@inject HttpContext;改用HttpContextAccessor并检查HttpContext?.Request?.Headers是否非空 - 静态资源(如 CSS/JS)仍需通过
_Host.cshtml引入;RenderMode.Server不改变资源加载路径,但会影响base.OnInitialized()中对NavigationManager.BaseUri的解析时机
最易被忽略的是:SignalR 连接失败时,页面看起来“已加载”,但所有点击都静默失效——务必在浏览器控制台检查 blazor.server.js 加载是否 404,以及 /_blazor/negotiate 是否返回 200。










