support-prerendering

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Support Prerendering

支持预渲染

How Prerendering Works

预渲染的工作原理

Prerendering is on by default for all interactive render modes. The server renders the component as static HTML and ships it to the browser immediately. Then the interactive runtime (Server/WebAssembly) loads and re-renders the component with full interactivity.
This means:
  • OnInitializedAsync
    runs twice — once during prerender (static), once when the interactive runtime attaches.
  • OnAfterRenderAsync
    is NOT called during prerender — only after the interactive render.
  • Internal navigation between interactive pages (interactive routing) skips prerendering — prerendering only happens on full page loads.
所有交互式渲染模式默认启用预渲染。服务器将组件渲染为静态HTML并立即发送到浏览器。随后,交互式运行时(Server/WebAssembly)加载并重新渲染组件,使其具备完整交互性。
这意味着:
  • OnInitializedAsync
    运行两次——一次在预渲染(静态)阶段,一次在交互式运行时连接后。
  • OnAfterRenderAsync
    在预渲染阶段不会被调用——仅在交互式渲染后调用。
  • 交互式页面之间的内部导航(交互式路由)会跳过预渲染——预渲染仅在整页加载时发生。

Step 1 — Read the Project's AGENTS.md

步骤1 — 阅读项目的AGENTS.md

Check the project's
AGENTS.md
for the Interactivity Mode and Interactivity Scope:
ModePrerendering applies?
None (Static SSR)No — there's no interactive handoff
ServerYes
WebAssemblyYes
AutoYes
If the mode is
None
, this skill doesn't apply.
查看项目的
AGENTS.md
文件,了解交互模式交互范围
模式是否应用预渲染?
None(静态SSR)否——不存在交互式切换
Server
WebAssembly
Auto
如果模式为
None
,则本技能不适用。

Persist State Across Prerender → Interactive

跨预渲染→交互式阶段持久化状态

The most common prerendering problem: data loaded in
OnInitializedAsync
during prerender is thrown away and re-fetched when the interactive runtime attaches. This causes flicker and duplicate API/DB calls.
最常见的预渲染问题:预渲染阶段在
OnInitializedAsync
中加载的数据会被丢弃,在交互式运行时连接后会重新获取。这会导致页面闪烁和重复的API/数据库调用。

Recommended:
[PersistentState]
attribute

推荐方案:
[PersistentState]
特性

Annotate properties to automatically serialize during prerender and restore on interactive activation:
razor
@page "/forecasts"
@rendermode InteractiveServer

<h1>Weather</h1>

@if (Forecasts is null)
{
    <p>Loading...</p>
}
else
{
    @foreach (var f in Forecasts)
    {
        <p>@f.Date: @f.TemperatureC°C</p>
    }
}

@code {
    [PersistentState]
    public WeatherForecast[]? Forecasts { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Forecasts ??= await ForecastService.GetForecastsAsync();
    }
}
The
??=
pattern is critical — it means "only fetch if the property wasn't already restored from prerender state."
为属性添加注解,使其在预渲染期间自动序列化,并在交互式激活时恢复:
razor
@page "/forecasts"
@rendermode InteractiveServer

<h1>Weather</h1>

@if (Forecasts is null)
{
    <p>Loading...</p>
}
else
{
    @foreach (var f in Forecasts)
    {
        <p>@f.Date: @f.TemperatureC°C</p>
    }
}

@code {
    [PersistentState]
    public WeatherForecast[]? Forecasts { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Forecasts ??= await ForecastService.GetForecastsAsync();
    }
}
??=
模式至关重要——它表示“仅在属性未从预渲染状态恢复时才进行获取”。

Multiple instances of the same component

同一组件的多个实例

When the same component type appears multiple times, use
@key
to disambiguate state:
razor
@foreach (var item in items)
{
    <ItemCard @key="item.Id" />
}
当同一组件类型多次出现时,使用
@key
区分状态:
razor
@foreach (var item in items)
{
    <ItemCard @key="item.Id" />
}

Advanced:
PersistentComponentState
service

进阶方案:
PersistentComponentState
服务

For complex scenarios (dynamic keys, custom serialization), use the imperative API:
csharp
@inject PersistentComponentState ApplicationState

@code {
    private List<Order>? orders;

    protected override async Task OnInitializedAsync()
    {
        ApplicationState.RegisterOnPersisting(PersistOrders);

        if (!ApplicationState.TryTakeFromJson<List<Order>>("orders", out var restored))
        {
            orders = await OrderService.GetOrdersAsync();
        }
        else
        {
            orders = restored;
        }
    }

    private Task PersistOrders()
    {
        ApplicationState.PersistAsJson("orders", orders);
        return Task.CompletedTask;
    }
}
对于复杂场景(动态键、自定义序列化),使用命令式API:
csharp
@inject PersistentComponentState ApplicationState

@code {
    private List<Order>? orders;

    protected override async Task OnInitializedAsync()
    {
        ApplicationState.RegisterOnPersisting(PersistOrders);

        if (!ApplicationState.TryTakeFromJson<List<Order>>("orders", out var restored))
        {
            orders = await OrderService.GetOrdersAsync();
        }
        else
        {
            orders = restored;
        }
    }

    private Task PersistOrders()
    {
        ApplicationState.PersistAsJson("orders", orders);
        return Task.CompletedTask;
    }
}

Disable Prerendering

禁用预渲染

Disable prerendering when a component depends on browser APIs immediately or when the prerender+interactive double render causes problems you can't solve with
[PersistentState]
.
当组件需要立即依赖浏览器API,或者预渲染+交互式双重渲染导致的问题无法通过
[PersistentState]
解决时,可禁用预渲染。

On a component definition

在组件定义上禁用

razor
@rendermode @(new InteractiveServerRenderMode(prerender: false))
Replace
InteractiveServerRenderMode
with
InteractiveWebAssemblyRenderMode
or
InteractiveAutoRenderMode
as needed.
razor
@rendermode @(new InteractiveServerRenderMode(prerender: false))
根据需要将
InteractiveServerRenderMode
替换为
InteractiveWebAssemblyRenderMode
InteractiveAutoRenderMode

On a component instance

在组件实例上禁用

razor
<MyChart @rendermode="new InteractiveServerRenderMode(prerender: false)" />
razor
<MyChart @rendermode="new InteractiveServerRenderMode(prerender: false)" />

On the entire app

在整个应用中禁用

In
App.razor
:
razor
<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
Note: A parent's prerendering setting overrides children. If
<Routes>
disables prerendering, individual pages cannot re-enable it.
App.razor
中:
razor
<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />
注意:父组件的预渲染设置会覆盖子组件。如果
<Routes>
禁用了预渲染,单个页面无法重新启用它。

Exclude Pages from Interactive Routing

将页面排除在交互式路由之外

In a globally interactive app, some pages may need
HttpContext
(cookies, request headers, response status codes). These pages must render via static SSR, not inside the interactive runtime.
Use
[ExcludeFromInteractiveRouting]
:
razor
@page "/privacy"
@attribute [ExcludeFromInteractiveRouting]

<h1>Privacy Policy</h1>
This forces a full page reload when navigating to this page, exiting interactive routing. The page renders as static SSR with full
HttpContext
access.
In
App.razor
, conditionally apply the render mode:
razor
<!DOCTYPE html>
<html>
<head>
    <HeadOutlet @rendermode="RenderModeForPage" />
</head>
<body>
    <Routes @rendermode="RenderModeForPage" />
    <script src="_framework/blazor.web.js"></script>
</body>
</html>

@code {
    [CascadingParameter]
    public HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? RenderModeForPage =>
        HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;
}
Replace
InteractiveServer
with the app's configured render mode.
在全局交互式应用中,某些页面可能需要
HttpContext
(Cookie、请求头、响应状态码)。这些页面必须通过静态SSR渲染,而不是在交互式运行时内部渲染。
使用
[ExcludeFromInteractiveRouting]
特性:
razor
@page "/privacy"
@attribute [ExcludeFromInteractiveRouting]

<h1>Privacy Policy</h1>
这会强制导航到该页面时进行整页重载,退出交互式路由。页面将以静态SSR方式渲染,并可完全访问
HttpContext
App.razor
中,有条件地应用渲染模式:
razor
<!DOCTYPE html>
<html>
<head>
    <HeadOutlet @rendermode="RenderModeForPage" />
</head>
<body>
    <Routes @rendermode="RenderModeForPage" />
    <script src="_framework/blazor.web.js"></script>
</body>
</html>

@code {
    [CascadingParameter]
    public HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? RenderModeForPage =>
        HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;
}
InteractiveServer
替换为应用配置的渲染模式。

Detect Prerender vs Interactive at Runtime

在运行时检测预渲染与交互式状态

Use
RendererInfo
to guard code that should only run interactively:
csharp
protected override async Task OnInitializedAsync()
{
    if (RendererInfo.IsInteractive)
    {
        // Only runs during the interactive render, not during prerender
        await StartSignalRConnection();
    }
}
RendererInfo
properties:
  • IsInteractive
    false
    during prerender,
    true
    after interactive runtime attaches
  • Name
    "Static"
    during prerender,
    "Server"
    or
    "WebAssembly"
    when interactive
使用
RendererInfo
保护仅应在交互式阶段运行的代码:
csharp
protected override async Task OnInitializedAsync()
{
    if (RendererInfo.IsInteractive)
    {
        // 仅在交互式渲染阶段运行,预渲染阶段不执行
        await StartSignalRConnection();
    }
}
RendererInfo
的属性:
  • IsInteractive
    — 预渲染阶段为
    false
    ,交互式运行时连接后为
    true
  • Name
    — 预渲染阶段为
    "Static"
    ,交互式阶段为
    "Server"
    "WebAssembly"

Client Services Fail During Prerender

客户端服务在预渲染期间失败

Components in the
.Client
project prerender on the server. Services registered only in the client
Program.cs
(e.g.,
IWebAssemblyHostEnvironment
) won't be available during prerender.
Fix by one of:
  1. Register a matching service on the server — both
    Program.cs
    files provide the service
  2. Make the service optional — use constructor injection with a nullable default:
    public MyComponent(IMyService? svc = null)
  3. Create a service abstraction — interface in
    .Client
    , implementations in both projects
  4. Disable prerendering for that component
.Client
项目中的组件在服务器上进行预渲染。仅在客户端
Program.cs
中注册的服务(例如
IWebAssemblyHostEnvironment
)在预渲染期间不可用。
可通过以下方式修复:
  1. 在服务器上注册匹配的服务 — 两个
    Program.cs
    文件都提供该服务
  2. 将服务设为可选 — 使用构造函数注入并设置可空默认值:
    public MyComponent(IMyService? svc = null)
  3. 创建服务抽象 — 在
    .Client
    中定义接口,在两个项目中实现
  4. 禁用该组件的预渲染

Don'ts

注意事项

  • Don't call JS interop in
    OnInitializedAsync
    — JS isn't available during prerender. Use
    OnAfterRenderAsync(firstRender)
    .
  • Don't assume
    OnInitializedAsync
    runs once — it runs twice with prerendering. Always use
    [PersistentState]
    or
    ??=
    guards.
  • Don't use
    HttpContext
    in interactive components — it's only available during the static prerender, not during the interactive lifetime. Use
    [ExcludeFromInteractiveRouting]
    for pages that need it.
  • Don't disable prerendering as a first resort — it hurts perceived load time and SEO. Use
    [PersistentState]
    to preserve state instead.
  • 不要在
    OnInitializedAsync
    中调用JS interop——预渲染期间JS不可用。请使用
    OnAfterRenderAsync(firstRender)
  • 不要假设
    OnInitializedAsync
    仅运行一次——在预渲染模式下它会运行两次。请始终使用
    [PersistentState]
    ??=
    进行保护。
  • 不要在交互式组件中使用
    HttpContext
    ——它仅在静态预渲染阶段可用,在交互式生命周期中不可用。对需要
    HttpContext
    的页面使用
    [ExcludeFromInteractiveRouting]
  • 不要将禁用预渲染作为首选方案——这会损害感知加载时间和SEO。请改用
    [PersistentState]
    来保留状态。