Pré-renderização de aplicativos Blazor - Como funciona - dicas e truques

Imagem de capa Pré-renderização de aplicativos Blazor - Como funciona - dicas e truques

Pré-renderização de aplicativos Blazor - Como funciona - dicas e truques

O que é pré-renderização?

Antes de mais nada, temos que esclarecer o que significa "pré-renderização" no Blazor. Por que alguém faria isso? Além disso, isso se aplica ao Blazor Client e ao Blazor Server?

Como seria uma solicitação inicial normal sem pré-renderização?

  • Nosso usuário acessa nossa página. Assim, nosso navegador irá para o endereço fornecido e carregará nosso arquivo de índice.
  • Nosso servidor agora servirá coisas diferentes dependendo se usarmos Cliente ou Servidor º Cliente: Estamos servindo um blazor.webassembly.js que inicializa o download de seus assemblies de aplicação e assemblies .NET que são então compilados para WebAssembly e executados º Servidor: Estamos servindo um blazor.server.js* que cria uma conexão SignalR do seu navegador para o servidor web. A conexão SignalR basicamente despacha suas entradas para o servidor e retorna o novo "HTML-Diff"
  • A versão inicializada renderizará seu componente e o exibirá para o usuário

Qual é o problema aqui? Temos 2 problemas. Um para o blazor do lado do cliente e outro para o lado do servidor:

  • Lado do cliente: Antes que o usuário possa interagir com seu site, todo o conteúdo deve ser baixado. Isso pode demorar um pouco
  • Lado do cliente / Lado do servidor: Como precisamos de JavaScript e SignalR ou WebAssembly, você terá dificuldade em otimizar seu site para os mecanismos de pesquisa. Eles muitas vezes não executam JavaScript ou terão problemas com WebSockets / WebAssembly.

A solução: Pré-renderização Pré-renderização significa apenas que pegamos a primeira solicitação e fingimos ser como um site ASP.NET Core normal. Nós apenas renderizamos tudo e retornaremos o conteúdo (html estático) para o cliente (ao lado das outras coisas descritas acima). É assim que se parece:

Benefícios

  • Para o blazor do lado do cliente, mostramos o site inicial ao usuário sem baixar o pacote .NET "grande". Assim, o usuário tem uma experiência muito mais suave
  • Para ambos: Habilitamos o SEO.

Como habilitar isso?

Pois é super fácil. Você vai para o seu _Host.cshtml e adiciona o render-mode como o seguinte

<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

Para o lado do cliente/WebAssembly

<component type="typeof(App)" render-mode="ServerPrerendered" />

Para o lado do servidor Blazor.

A desvantagem

Agora o diagrama está mostrando dois "displays" / "renders", certo? Sim. Dê uma olhada no seguinte componente:

@page "/"

@inject IUserRepository userRepository

@foreach(var user in users)
{
     <ShowUserInformation user="@item"></ShowUserInformation>
}

@code {
     private List<User> users = new();
     
     protected override async Task OnInitializedAsync()
     {
          Console.WriteLine("I was called");
          users = await userRepository.GetAllUsers();
     }
}

Se você habilitar a pré-renderização e olhar no console de saída, verá que eu fui chamado duas vezes. A razão é simples. A primeira vez que OnInitializedAsync é chamado acontece no servidor. Portanto, o servidor tem que fazer todo o trabalho para criar o site html estático. Depois que terminarmos e o conteúdo for enviado ao usuário, o segundo OnInitializedAsync entrará em ação. Como agora os circuitos WebAssembly ou SignalR são iniciados, eles farão a mesma coisa novamente. Eles não "sabiam" que já estava renderizado.

Então, se você fizer trabalho pesado, será feito duas vezes! Obviamente um site para baixo. Além disso: seu usuário pode ver uma oscilação óbvia à medida que a página é atualizada.

Há algo que possamos fazer?

.NET 6 para o resgate - conheça persist-component-state

Agora o .NET6 traz um auxiliar exatamente para esse caso: persist-component-state. Simplificado é um cache preenchido pelo servidor e também enviado para o cliente. Então, uma espécie de cache pré-preenchido.

Vamos dar uma olhada em como ele funciona. A configuração é um pouco diferente para o Blazor do lado do cliente e do lado do servidor.

Configuração

Agora vamos dar uma olhada no lado do cliente primeiro. Vá para o seu Pages/_Host.cshtml e adicione o seguinte logo antes do final da sua tag body.

<body>
    <component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
    <persist-component-state />
</body>

Portanto, o simples < persist-component-state /> é totalmente suficiente. Agora, como vamos usá-lo?

Vamos dar uma olhada no nosso "novo" Index.razor

@page "/"

@implements IDisposable 
@inject PersistentComponentState applicationState 
@inject IUserRepository userRepository

@foreach(var user in users) 
{
     <ShowUserInformation user="@item"></ShowUserInformation>
}

@code {
     private  const  string cachingKey = "something_unique"; 
     private List<User> users = new(); 
     private PersistingComponentStateSubscription persistingSubscription;
     
     protected override async Task OnInitializedAsync() 
     {
          persistingSubscription = applicationState.RegisterOnPersisting(PersistData);
          
          if (!applicationState.TryTakeFromJson<List<User>>(cachingKey, out  var restored))
          {
               users = restored;
          }
          else
          {
              users = await userRepository.GetAllUsers();
          }
     }
     
     public  void  Dispose()
     {
         persistingSubscription.Dispose();
     }
     
     private Task PersistData()
     {
         applicationState.PersistAsJson(cachingKey, users);
         return Task.CompletedTask;
     }
}

Agora vamos desembrulhar isso um pouco:

  • @inject PersistentComponentState applicationState este é o componente que faz o "cache" para nós.
  • Aprimoramos nosso OnInitializedAsync com persistingSubscription = applicationState.RegisterOnPersisting(PersistData);. A ideia é criar uma assinatura para que a gente adicione algo no cache que fique lá do lado do cliente também.
  • Se fecharmos nossa página da web / navegarmos em outro lugar, teremos que cancelar a assinatura. Isso é feito na mensagem Dispose. É por isso que adicionamos @implements IDisposable no topo
  • A parte seguinte é apenas: Temos sob a chave alguma entrada, se sim, retorne-a e preencha-a nos usuários, caso contrário, vá para o repositório.
if (!applicationState.TryTakeFromJson<List<User>>(cachingKey, out  var restored))
{
  users = restored;
}
else
{
    users = await userRepository.GetAllUsers();
}

Esteja ciente de que é muito importante ter a mesma chave para salvar e restaurar as entradas de cache. Se você já usou o IMemoryCache, está bem ciente.

Recursos

Se você quiser saber mais sobre este assunto, dê uma olhada no seguinte: