.NET 8 Blazor component visibly loading twice? Check your prerendering
December 12, 2023 · 5 minute read · Tags: blazor
Data loading twice in your Blazor .NET 8 app?
Chances are it’s down to prerendering.
By default in .NET 8 your interactive components are rendered twice:
Once on the server (prerendering), where HTML is returned so the browser can show something nice and quickly.
Then your component will be rendered again using your preferred interactive render mode (server or WASM).
The problem is, if you’re fetching data in your component, it’s going to be fetched for both renders.
You can often see this in the UI, with a visible flash as the prerendered content is replaced with the “new data” when your component is rendered for a second time.
So what to do about it? Turns out there are two main options at your disposal:
- Disable prerendering
- Persist state between the first and second renders
Disable prerendering
The quick and easy option is to disable prerendering for your component.
@page "/"
@rendermode @(new InteractiveServerRenderMode(false))
<h1>Hello</h1>
Here we’ve specified that the render mode for this component is Interactive Server. The false
parameter switches prerendering off (for this component).
Now it will render once, and once only, using Interactive Server render mode.
Persist state between renders
But of course prerendering can be a useful thing.
It ensures your users see something in the browser even while Blazor Server/WASM loads up in the background.
So the other option is to persist the state you fetch during the first render (prerender) and use it when your component renders again (using one of .NET 8’s interactive modes).
The key to this is Blazor’s PersistentComponentState
to both store, and then retrieve your component’s important state.
Take this component which greets the logged in user.
We’re fetching the user’s name from a UserService
service (which, in a real app would likely identify the logged in user, and fetch their details from a database.)
Profile.razor
@page "/"
@inject PersistentComponentState ApplicationState
@inject UserService UserService
@implements IDisposable
Welcome @_username
@code {
private PersistingComponentStateSubscription _subscription;
private string? _username;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(Persist);
var foundInState = ApplicationState
.TryTakeFromJson<string>("userName", out var userName);
_username = foundInState
? userName
: UserService.GetUserName();
}
private Task Persist()
{
ApplicationState.PersistAsJson("userName", _username);
return Task.CompletedTask;
}
public void Dispose()
{
_subscription.Dispose();
}
}
There’s a little bit going on here, so let’s break it down.
First, we inject an instance of the PersistingComponentState
service, and store it in a field called ApplicationState
.
In OnInitializedAsync
we then use its RegisterOnPersisting
method.
This enables us to register a callback which Blazor can invoke at the right time to store our state (just before it returns the rendered HTML to the client).
In this case we register our Persist
method as a callback.
Here’s the flow for that first part of the process:
First Render (prerender)
Component participant UserService participant PersistentComponentState end Browser ->>+ Server: HTTP Request /Profile Server ->>+ UserService: Fetch user details UserService -->>+ Server: User Details note over Server: Render Server ->> PersistentComponentState: Store user details Server ->> Browser: HTML
When the component is rendered again (this time using Blazor Server or WASM) we can fetch and use this persisted state.
That’s what the rest of the code in OnInitializedAsync
takes care of.
protected override async Task OnInitializedAsync()
{
...
var foundInState = ApplicationState
.TryTakeFromJson<string>("userName", out var userName);
_username = foundInState
? userName
: UserService.GetUserName();
}
Here we try to fetch the current value of UserName
from our persistent component state (ApplicationState
).
If we find it, happy days, we can use take its value and assign it to our _username
field.
If we don’t find a value in ApplicationState
we’re probably rendering this component for the first time.
In that case we want to look up the user’s details using UserService
then assign the resulting value to _username
Because we registered Persist
as a callback via ApplicationState.RegisterOnPersisting
we can be sure it will be invoked at the right time to store the value of _username
(which we can then access in subsequent renders).
private Task Persist()
{
ApplicationState.PersistAsJson("userName", _username);
return Task.CompletedTask;
}
When our component renders the second time, the following code will retrieve the persisted state:
var foundInState = ApplicationState
.TryTakeFromJson<string>("userName", out var userName);
Meaning we can use that, and skip the call to UserService
Second Render (Server Interactive)
Component participant PersistentComponentState participant UserService end Browser ->>+ Server: Render Profile
(Server Interactive) Server ->> PersistentComponentState: Try Get User Details PersistentComponentState -->> Server: Persisted User Details note over Server: Render Server ->> Browser: DOM Diff
Properly disposing of the subscription
Finally, it’s important to clean up the subscription when our component is disposed.
Without this we’ll have subscriptions continue to exist even after the component that needed it has long since been disposed off.
This could cause a memory leak in your application, with each lingering subscription consuming resources long after it’s needed.
To solve that we can make our component implement IDisposable
.
This requires us to implement a Dispose
method which Blazor will invoke when the component is disposed.
There we can call the Dispose
method on our _subscription
field to ensure it is cleaned up at the same time as our component.
Summary
Prerendering makes for a faster initial load for your users.
But it can also lead to your component fetching data twice (once during prerendering, and again when rendered using one of the interactive render modes in .NET 8).
You can switch prerendering off if you don’t need it.
Alternatively, you can persist state during the first render, and use it during the second, to save making multiple calls to fetch the exact same data.
Persisting the state between renders adds more ‘plumbing’ code to your component, but ensures you only fetch data once, and avoids the user seeing that ‘flash’ in the UI as your component updates with the new data (during the second render).