Επεξεργασία

Κοινή χρήση μέσω


Prerender ASP.NET Core Razor components

Note

This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 9 version of this article.

This article explains Razor component prerendering scenarios for server-rendered components in Blazor Web Apps.

Prerendering is the process of initially rendering page content on the server without enabling event handlers for rendered controls. The server outputs the HTML UI of the page as soon as possible in response to the initial request, which makes the app feel more responsive to users. Prerendering can also improve Search Engine Optimization (SEO) by rendering content for the initial HTTP response that search engines use to calculate page rank.

Persist prerendered state

Without persisting prerendered state, state used during prerendering is lost and must be recreated when the app is fully loaded. If any state is created asynchronously, the UI may flicker as the prerendered UI is replaced when the component is rerendered.

Consider the following PrerenderedCounter1 counter component. The component sets an initial random counter value during prerendering in OnInitialized lifecycle method. After the SignalR connection to the client is established, the component rerenders, and the initial count value is replaced when OnInitialized executes a second time.

PrerenderedCounter1.razor:

@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;

    protected override void OnInitialized()
    {
        currentCount = Random.Shared.Next(100);
        Logger.LogInformation("currentCount set to {Count}", currentCount);
    }

    private void IncrementCount() => currentCount++;
}

Run the app and inspect logging from the component. The following is example output.

Note

If the app adopts interactive routing and the page is reached via an internal enhanced navigation, prerendering doesn't occur. Therefore, you must perform a full page reload for the PrerenderedCounter1 component to see the following output. For more information, see the Interactive routing and prerendering section.

info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92

The first logged count occurs during prerendering. The count is set again after prerendering when the component is rerendered. There's also a flicker in the UI when the count updates from 41 to 92.

To retain the initial value of the counter during prerendering, Blazor supports persisting state in a prerendered page using the PersistentComponentState service (and for components embedded into pages or views of Razor Pages or MVC apps, the Persist Component State Tag Helper).

To preserve prerendered state, decide what state to persist using the PersistentComponentState service. PersistentComponentState.RegisterOnPersisting registers a callback to persist the component state before the app is paused. The state is retrieved when the app resumes.

The following example demonstrates the general pattern:

  • The {TYPE} placeholder represents the type of data to persist.
  • The {TOKEN} placeholder is a state identifier string. Consider using nameof({VARIABLE}), where the {VARIABLE} placeholder is the name of the variable that holds the state. Using nameof() for the state identifier avoids the use of a quoted string.
@implements IDisposable
@inject PersistentComponentState ApplicationState

...

@code {
    private {TYPE} data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = 
            ApplicationState.RegisterOnPersisting(PersistData);

        if (!ApplicationState.TryTakeFromJson<{TYPE}>(
            "{TOKEN}", out var restored))
        {
            data = await ...;
        }
        else
        {
            data = restored!;
        }
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("{TOKEN}", data);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

The following counter component example persists counter state during prerendering and retrieves the state to initialize the component.

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        persistingSubscription =
            ApplicationState.RegisterOnPersisting(PersistCount);

        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();

    private void IncrementCount() => currentCount++;
}

When the component executes, currentCount is only set once during prerendering. The value is restored when the component is rerendered. The following is example output.

Note

If the app adopts interactive routing and the page is reached via an internal enhanced navigation, prerendering doesn't occur. Therefore, you must perform a full page reload for the PrerenderedCounter2 component to see the following output. For more information, see the Interactive routing and prerendering section.

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96

By initializing components with the same state used during prerendering, any expensive initialization steps are only executed once. The rendered UI also matches the prerendered UI, so no flicker occurs in the browser.

The persisted prerendered state is transferred to the client, where it's used to restore the component state. During client-side rendering (CSR, InteractiveWebAssembly), the data is exposed to the browser and must not contain sensitive, private information. During interactive server-side rendering (interactive SSR, InteractiveServer), ASP.NET Core Data Protection ensures that the data is transferred securely. The InteractiveAuto render mode combines WebAssembly and Server interactivity, so it's necessary to consider data exposure to the browser, as in the CSR case.

Components embedded into pages and views (Razor Pages/MVC)

For components embedded into a page or view of a Razor Pages or MVC app, you must add the Persist Component State Tag Helper with the <persist-component-state /> HTML tag inside the closing </body> tag of the app's layout. This is only required for Razor Pages and MVC apps. For more information, see Persist Component State Tag Helper in ASP.NET Core.

Pages/Shared/_Layout.cshtml:

<body>
    ...

    <persist-component-state />
</body>

Interactive routing and prerendering

When the Routes component doesn't define a render mode, the app is using per-page/component interactivity and navigation. Using per-page/component navigation, internal† navigation is handled by enhanced routing after the app becomes interactive. †Internal in this context means that the URL destination of the navigation event is a Blazor endpoint inside the app.

The PersistentComponentState service only works on the initial page load and not across internal enhanced page navigation events.

If the app performs a full (non-enhanced) navigation to a page utilizing persistent component state, the persisted state is made available for the app to use when it becomes interactive.

If an interactive circuit has already been established and an enhanced navigation is performed to a page utilizing persistent component state, the state isn't made available in the existing circuit for the component to use. There's no prerendering for the internal page request, and the PersistentComponentState service isn't aware that an enhanced navigation has occurred. There's no mechanism to deliver state updates to components that are already running on an existing circuit. The reason for this is that Blazor only supports passing state from the server to the client at the time the runtime initializes, not after the runtime has started.

Additional work on the Blazor framework to address this scenario is under consideration for .NET 10 (November, 2025). For more information and community discussion of unsupported workarounds‡, see Support persistent component state across enhanced page navigations (dotnet/aspnetcore #51584). ‡Unsupported workarounds aren't sanctioned by Microsoft for use in Blazor apps. Use third-party packages, approaches, and code at your own risk.

Disabling enhanced navigation, which reduces performance but also avoids the problem of loading state with PersistentComponentState for internal page requests, is covered in ASP.NET Core Blazor routing and navigation.

Prerendering guidance

Prerendering guidance is organized in the Blazor documentation by subject matter. The following links cover all of the prerendering guidance throughout the documentation set by subject: