Learn how to reuse state between prerendering and establishing interactivity in Blazor components.
In Blazor, prerendering lets pages render quickly on the server, giving users useful content while the application becomes interactive. It also makes the initial HTML available to search engines.
However, prerendering comes with an important consequence: a component can be initialized twice. It is first rendered on the server during prerendering and then initialized again when the selected interactive render mode takes over. This applies not only to Interactive Server, but also to Interactive WebAssembly and Interactive Auto.
This can become a problem when a component performs expensive work during initialization, such as calling a database or an external API.
In this article, we’ll learn how to avoid that unnecessary work by persisting component state between prerendering and interactive rendering.
You can access the code on GitHub.
The Default Behavior of Page Components and Prerendering in Blazor
We often see code like this in page components:
The problem here is that with Prerending enabled, the OnInitializedAsync lifecycle method will be called twice. If the data provided by the service is the same, the user might not see anything change on the screen.
However, imagine the following implementation of the GetProductsAsync method:
This example uses randomly generated values for the Price property of each Product. What might seem a bit artificial at first makes total sense. Let me explain.
We simulate loading the stock from an external API or the price from a remote service. It is not guaranteed that the product costs the same all day, or that another user didn’t purchase a product between the two times GetProductsAsync is called.
If we add a log statement in the GetProductsAsync method, we will see the method is called twice during page load.
On the screen, the user will see two different prices for the same item. The first value appears quickly, then the second replaces it.
This is the screen after the prerendering.
And this is the screen after the component interactivity has been initialized.
Blazor Component Lifecycle with Prerendering
Let’s take a step back and look at how and why the OnInitializedAsync method is called twice when rendering a page in a Blazor web application with Prerendering enabled.
The user initiates an HTTP request by visiting the web application through the browser. The server prerenders the initial page and sends the HTML to the client, where the browser renders it.
Meanwhile, Blazor becomes interactive, and the component reinitializes. For example, on click, handlers are enabled.
The component is initialized once during prerendering and then initialized again when the interactive render mode takes over. As a result, OnInitializedAsync can run twice, causing code that performs data loading or other initialization work to execute twice.
This behavior is the same regardless of the rendering mode (InteractiveServer, InteractiveWebAssembly, InteractiveAuto).
Persisting The Prerendered State Between OnInitializedAsync Calls
Now that we know what’s happening, let’s learn how to fix the problem. In our example code, we want to fetch the data from the ProductService only once.
A simple solution is to store the data in the Data property during prerendering and keep it available so that we can reuse it when the component becomes interactive and the OnInitializedAsync method is called again.
First, let’s make sure we check that the Data property isn’t null before we access the ProductService.
But how do we persist the data in the Data property once we get it returned from the ProductService by calling its GetProductsAsync method?
That’s exactly the problem the PersistentState attribute helps solve in .NET 10.
We add the PersistentState attribute to the Data property.
That’s all we need to do. When you restart the application, you will notice that the prices do not change when reloading the Products page.
Under the hood, the data inside the Data property will be serialized so that it can be deserialized during the second OnInitializedAsync call. Blazor isn’t persisting the component itself. It’s persisting the value of the property.
Note: You can put log statements in the OnInitializedAsync lifecycle method and the GetProductsAsync method of the ProductService. You’ll notice that with the PersistentState attribute applied, there will be two OnInitializedAsync calls and only one GetProductsAsync call. On the contrary, without the PersistentState attribute, there will be a GetProductsAsync call for each OnInitializedAsync method call.
What Data Should We Persist?
Data returned from a backend API is an obvious candidate to be persisted between prerendering and component interactivity. But what about other data?
The following list is my personal guideline that can help you get a sense for when to use or avoid using the PersistentState attribute in your projects:
Good Candidates
- Data retrieved during prerendering
- Expensive-to-create serializable view-model data
- Read-only lookup data
- Initial form state
- Data needed to make the interactive UI match the prerendered UI
Bad candidates
- Passwords
- Secrets
- Huge datasets
- Sensitive information
- Objects containing infrastructure dependencies
- Things that should really live in a database
There’s an important security angle here. For client-side rendering, persisted state is exposed to the browser, so don’t include sensitive/private information.
With interactive server rendering, ASP.NET Core Data Protection protects the transferred data, but you still need to reason about what you’re persisting. InteractiveAuto and InteractiveWebAssembly need client-side exposure rules considered.
Tip: If the state has to reach an Interactive WebAssembly component, assume that the persisted representation can ultimately be observed by the client. Don’t use persistent component state as a mechanism for hiding secrets, authorization information, passwords or other sensitive data.
State in Blazor Web Applications
The PersistentState attribute solves exactly what this article shows. It persists the component state between prerendering and component interactivity.
In Blazor web applications, we need to handle different types of state. The PersistentState attribute is a small part of a big puzzle.
The following table shows different types of state, a specific example, and a best practice for handling that state.
The PersistentState attribute should only be used to persist the state between prerendering a component and its interactivity initialization.
Conclusion
Prerendering is an important part of building fast Blazor web applications, but it also means that components can be initialized more than once as they transition from prerendered HTML to an interactive application.
If the component initialization performs expensive work, such as retrieving data from a backend service or database, that work can be executed twice when a component becomes interactive.
The PersistentState attribute provides a simple way to preserve the data returned by a backend service between these two phases, allowing the interactive component to reuse data that was already retrieved during prerendering.
However, PersistentState isn’t a replacement for Blazor’s other state-management approaches. It is specifically useful for carrying state across the boundary between prerendering and interactive rendering.
If you want to learn more about Blazor development, watch my free Blazor Crash Course on YouTube. And stay tuned to the Telerik blog for more Blazor Basics.












