Problem of server prerender
Blazor server prerender allows to response not a empty index page of spa to client, but fully rendered page with information like using razor page. Client sees information immediatly and search engine should be happy, however it does not work in that way. The reason how blazor work:
- Your page request
blazor.js
blazor.js
downloads .net runtime and your applicationdlls
- Webassembly runs .net runtime
- .Net runs your app
When app is started, it does not know that it was prerendered and call method OnInitialized of your page. Which requests information from backend. While it is waiting for response, blazor shows empty page. In the result, client sees strange blinking page.
.Net 6
You could reinvent the wheel and try to transfer state within page source. For example, json in script section. However, you get different problems like calling js interoper in sync way, because instead your anyway get empty page, or freezing page when you call js in sync way and it blocks ui thread while waiting result.
Fortunately, microsoft adds ability to persist state with new component ` and
ComponentApplicationState` service.
Let’s trying use the new component in start template project.
All we need to do some changes to allow server prerender and change our FetchData.razor
page in that way:
@page "/fetchdata"
@using BlazorPersistedState.Shared
@inject HttpClient Http
@implements IDisposable
@inject ComponentApplicationState ApplicationState
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
ApplicationState.OnPersisting += PersistForecasts;
if (!ApplicationState.TryTakeAsJson<WeatherForecast[]>("fetchdata", out var data))
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
else
{
forecasts = data;
}
}
private Task PersistForecasts()
{
ApplicationState.PersistAsJson("fetchdata", forecasts);
return Task.CompletedTask;
}
void IDisposable.Dispose()
{
ApplicationState.OnPersisting -= PersistForecasts;
}
}
The main logic here is that if we have any state let’s take this state without extra requests to backend.
if (!ApplicationState.TryTakeAsJson<WeatherForecast[]>("fetchdata", out var data))
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
else
{
forecasts = data;
}
As always, you can find source code of the example here.