Does .NET 6 fix Blazor Prerendering?
April 19, 2021 · 6 minute read · Tags: blazor | prerendering
Nobody wants to see Loading… when they visit your site, but that’s what they’ll get if you use Blazor WASM for your site.
Unless, that is, you employ prerendering.
Ways to avoid loading…
Blazor Server apps load near instantly in the browser, but require a permanent connection to a server (hence the name).
This works well for ‘Line Of Business’ apps but less so for web sites which are to be consumed by the general public (where latency and scaling up can become a problem).
Blazor WASM provides a viable alternative, liberating your app from needing a server (except of course for fetching or persisting data).
But here we run smack bang into one of the big drawbacks of Blazor WASM; your users have to wait a variable amount of time for the application to ’load’ before they can interact with it.
This limitation makes it a sub-optimal choice for many use cases. Take for example a blog. It kind of sucks if your users hit a link to read an article you’ve written only to be faced with a loading spinner while .NET downloads to their browser before they can start reading anything at all.
It also makes things like SEO and social media cards (like those previews you see when a link is shared on Twitter) difficult because every request to your site has to navigate this initial download first.
Which brings us to prerendering.
It’s possible to configure your Blazor WASM application so that static HTML (generated on the server) is returned in response to the first request made, making for a super snappy first load.
This buys you some time; while your user’s are happily looking at the content that’s already appeared in front of them, their browser can silently download the rest of the assets required to make the site ‘interactive’ in the browser.
Sounds great, what’s the catch?
This sounds like the perfect compromise, so why isn’t everyone doing it?
One reason is that server prerendering rules out hosting your site using static hosting providers like Netlify (because you need a server to handle that first request).
But perhaps the biggest limitation with prerendering (before .NET 6) is the way that data is potentially retrieved twice.
When your users access a page on your site which retrieves data, they will see the page as it was initially rendered on the server, then the page/component will be initialized/rendered again in the browser (when the assets have finished downloading).
As part of this process the data is retrieved again, often leading to a ‘flash’ as the screen renders the data for a second time.
.NET 6 addresses this problem and opens the doors to prerendering for many more applications.
Preserving state between renders
.NET 6 enables you to persist data which has been used for the first render (on the server), then retrieve and use this data again when the app is rendered in the browser.
Taking the standard weather data example from the Blazor project template, here’s how it works.
If you’ve set up your Blazor app for prerendering you’ll probably have a _Host.cshtml
page in the Server project.
Make sure to add a call to persist the component state using <persist-component-state />
.
<body>
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
<persist-component-state />
<script src="_framework/blazor.webassembly.js"></script>
</body>
This needs to be included after the component(s) whose state you wish to persist. In this case we’re prerendering the entire application (App
) so it makes sense to include <persist-component-state>
directly after the App
component is declared.
Next you’ll need to inject ComponentApplicationState
into your component (which is FetchData
in this case).
FetchData.razor
@inject PersistentComponentState ApplicationState
Now you can use ApplicationState
to store and retrieve data.
The first step is to make sure any data is persisted when the component is prerendered on the server. For that you can hook into the OnPersisting
event.
private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(PersistForecasts);
}
OnPersisting
will fire once your component has been loaded and rendered (which in a prerendering scenario will happen on the server as part of the initial prerender).
private Task PersistForecasts()
{
ApplicationState.PersistAsJson("fetchData", forecasts);
return Task.CompletedTask;
}
Here we persist the forecasts data, giving it a key of fetchData
.
The next step is to actually check for, and use, this data if it’s available. This is the step that will typically occur when the component is rendered for the second time in the browser.
private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(PeristForecasts);
if (ApplicationState.TryTakeFromJson<WeatherForecast[]>("fetchData", out var stored))
forecasts = stored;
else
forecasts = await WeatherForecastService.GetForecastAsync();
}
We now look for that persisted fetchData
data (from the first render on the server) and use it if it exists.
Otherwise we can continue as normal and retrieve the data (in this case via the WeatherForecastService
). In this scenario this would only happen once, as part of the server prerender.
The result appears seamless; when you visit the page you’ll see it come back quickly (because of the prerendering) and likely won’t even notice the second render (in the browser) because nothing on screen will change.
One last point, make sure to dispose of the subscription (to avoid any potential memory leaks). You can do this by making your component implement IDisposable
, then calling _subscription.Dispose()
.
Complete Example
Here’s the final code (omitting the HTML markup) with the key lines which run on the server (during prerendering) highlighted.
@page "/fetchdata"
@using UI.Shared
@inject IWeatherForecastService WeatherForecastService
@inject PersistentComponentState ApplicationState
@implements IDisposable
<!-- html here -->
@code {
private WeatherForecast[] forecasts;
private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = ApplicationState.RegisterOnPersisting(PersistForecasts);
if (ApplicationState.TryTakeFromJson<WeatherForecast[]>("fetchData", out var stored))
forecasts = stored;
else
forecasts = await WeatherForecastService.GetForecastAsync();
}
private Task PersistForecasts()
{
ApplicationState.PersistAsJson("fetchData", forecasts);
return Task.CompletedTask;
}
public void Dispose()
{
_subscription.Dispose();
}
}
In Summary
With .NET 6 you’re able to use PersistentComponentState
to grab a snapshot of your component’s state during the initial prerender, then use it to hydrate the component when it’s rendered for the second time in the browser.
This avoids retrieving the same data twice and avoids flashes of content during the second render, resulting in a more seamless experience for your users.
Further reading
- Prerendering your .NET 5 Blazor WASM applications (part 1)
- Prerendering your Blazor WASM application with .NET 5 (part 2 - solving the missing HttpClient problem)
All posts in the
To .NET 5 and beyond series.
- Update the HTML head from your Blazor components
- From site.css to component styles
- Prerendering your Blazor WASM application with .NET 5 (part 1)
- Prerendering your Blazor WASM application with .NET 5 (part 2 - solving the missing HttpClient problem)
- Render Blazor WASM components in your existing MVC/Razor Pages applications
- Does .NET 6 fix Blazor Prerendering?