Blazor Render Modes

Os Blazor render modes, introduzidos no .NET 8, permitem escolher como cada página ou componente é desenhado, e isso mudou completamente a forma de pensar aplicações com Blazor.

Por que os Blazor render modes mudaram o jogo

Se você me perguntasse sobre o Blazor alguns anos atrás, eu diria que era legal, mas que ainda preferia o Angular. Afinal, trocar um framework consolidado por uma tecnologia nova não parecia uma boa ideia.

O tempo passou e o Blazor amadureceu. Mesmo assim, minha recomendação era usar o Blazor principalmente quando você já tinha código em C#.

Isso porque, mesmo trabalhando no modelo SPA, o Blazor, assim como Angular, React e Vue, não tinha um bom suporte a SSR. Na prática, é o SSR que sustenta o SEO de um site.

No caso do balta, por exemplo, o Blazor seria incrível na área de compras, trazendo mais interatividade para o usuário.

Da mesma forma, no Player, que é a área onde o aluno assiste aos cursos, o Blazor seria devastador. Ainda assim, todo o nosso site roda em Razor Pages.

Essa decisão aconteceu porque nossa aplicação é relativamente pequena. Portanto, não havia motivo para quebrá-la em três partes só para ter o Blazor no pagamento e no Player.

Muitos negócios se seguraram nesses argumentos. No entanto, com os Blazor render modes, o jogo mudou completamente.

O que são Blazor render modes

No Blazor do .NET 8, a Microsoft incluiu uma opção chamada render mode, ou modo de renderização, para componentes e páginas.

Essa opção define o modelo de renderização de uma página ou de um componente. Além disso, você também pode aplicar um único modo para a aplicação inteira.

Particularmente, opto pela flexibilidade. Então, só escolheria um único render mode do Blazor para a aplicação toda em um cenário muito específico.

Segundo a documentação oficial de render modes do ASP.NET Core, todo componente de um Blazor Web App adota um modo que define onde ele roda e se ele é interativo.

Os quatro modos disponíveis

Ao todo, temos quatro modos de renderização. Cada um tem suas particularidades, que afetam a forma como páginas e componentes aparecem na tela:

  • Static SSR, com a variação Streaming Rendering
  • Interactive Server
  • WebAssembly interativo (Interactive WebAssembly)
  • Auto interativo (Interactive Auto)

O Static SSR roda no servidor e não tem interatividade. Já o Interactive Server também roda no servidor, mas mantém uma conexão em tempo real com o navegador.

O Interactive WebAssembly, por sua vez, roda no cliente. Por fim, o Interactive Auto começa no servidor e passa para o cliente depois que o navegador baixa o runtime do .NET.

Static SSR no Blazor

Já comentamos sobre o SSR, ou Server Side Rendering. Agora, vamos entender na prática o que ele é e como ele funciona.

Como funciona o processo de SEO

Uma das validações que o Google faz para indexar e ranquear nossas páginas é verificar o título e o conteúdo de cada uma.

Quando buscamos por ".NET", por exemplo, o buscador verifica as páginas com esse termo no título. Em seguida, ele também analisa o corpo da página.

Essa última etapa é a mais complexa para nós. Afinal, se a página não tiver um corpo condizente com o título, ela despenca no ranking.

Vale lembrar que, no modelo WASM puro, o HTML final nasce no navegador. Em outras palavras, o servidor entrega uma página quase vazia para o robô de busca.

Além disso, os render modes funcionam por componente ou página. Então, seja criativo, porque nem todo componente precisa de SSR.

Como usar SSR nos Blazor render modes

Lembra do exemplo em que trabalhamos no Counter e adicionamos a diretiva @rendermode InteractiveServer? Ela é justamente o que torna um componente interativo.

Se sua página ou componente não declara de forma explícita que é interativo, o Blazor automaticamente usa o SSR estático. Entre os Blazor render modes, esse é o comportamento padrão.

Tomando como base a página Weather do template, que carrega dados de um serviço, podemos comentar o atributo StreamRendering ou passar false como parâmetro.

@* @attribute [StreamRendering(true)] *@

@* ou *@

@attribute [StreamRendering(false)]
Página Weather de um Blazor Web App exibindo a tabela de previsão do tempo após o carregamento com SSR estático
Weather carregada com SSR estático.

Porém, ao inspecionar a página, vemos que os dados estão no HTML. Ou seja, se esse conteúdo fosse relevante para o SEO, estaríamos bem.

Inspeção do HTML da página Weather mostrando os dados da tabela no código gerado pelo servidor com Blazor render modes em SSR
Dados presentes no HTML gerado.

Nesse cenário, o servidor gera toda a página, ou seja, HTML, CSS e JS, e só depois envia o resultado para o navegador.

É o mesmo processo que o ASP.NET Razor Pages executa. Talvez seja hora de migrar o balta para Blazor.

Streaming rendering

O Streaming Rendering executa um processo parecido, porém mais inteligente. Primeiro, ele entrega a página e, em seguida, envia os dados.

Para utilizá-lo, basta aplicar o atributo StreamRendering(true) ao componente, conforme o exemplo abaixo.

@attribute [StreamRendering(true)]

No .NET 8, o parâmetro true é obrigatório. A partir do .NET 9, o valor padrão passou a ser true, então o atributo sem parâmetro também funciona.

Para visualizar o efeito com clareza, recomendo aumentar o atraso na recuperação dos dados dentro do componente Weather.

// Linha 45 do Weather.razor
await Task.Delay(5000);

Essa página simula um cenário comum: ler dados lentos de um banco ou serviço.

Ao executar a aplicação e navegar para a página de clima, o conteúdo aparece, mas os dados ainda não. Enquanto isso, a tela exibe uma mensagem de Loading.

Página Weather exibindo a mensagem Loading enquanto o Streaming Rendering dos Blazor render modes aguarda os dados
Layout entregue antes dos dados.

A principal diferença para o SSR estático é o tempo de resposta. No exemplo anterior, a página só aparecia quando os dados estavam completos.

Neste caso, assim que os dados chegam do banco ou do serviço, eles vão para a tela e atualizam o componente ou a página.

Página Weather com a tabela de previsão do tempo preenchida depois que os dados chegaram via Streaming Rendering
Tabela preenchida após o streaming.

Porém, se você precisa dessas informações para uma boa indexação, é melhor olhar o HTML antes de decidir.

Código fonte da página Weather com Streaming Rendering mostrando apenas a mensagem Loading no HTML inicial
HTML com o estado inicial.

Como podemos ver, os dados entraram depois do carregamento da página. Por isso, o HTML de origem continua com o conteúdo inicial.

Dessa forma, o HTML sempre mostra o conteúdo que o servidor enviou na primeira resposta via SSR.

<h1>Weather</h1>

<p>This component demonstrates showing data.</p><p><em>Loading...</em></p><!--/bl:13--></article></main></div>

Como essa mágica acontece

Sempre que o Blazor identifica um componente com StreamRendering, ele renderiza tudo o que já tem em mãos.

No caso da página Weather, ele ainda não tem os dados. Então, ele envia a página e continua aguardando as informações adicionais.

Assim que os dados chegam, o Blazor atualiza a tela, porque a página continua conectada ao servidor.

De acordo com a documentação de renderização de componentes, o servidor envia o conteúdo atualizado pela mesma conexão da resposta e aplica as mudanças no DOM.

Ao inspecionar a execução da página, vemos um item de desconexão depois que todos os dados chegam.

Aba de rede do navegador mostrando a conexão da página Weather sendo encerrada após o Streaming Rendering
Conexão encerrada após os dados.

Isso significa que, mesmo com a página na tela, ainda havia uma conexão ativa com o servidor. Ela só fechou momentos depois.

Vale um alerta: hosts que fazem buffer da resposta anulam esse efeito. Nesse caso, a página simplesmente carrega sem o streaming.

Interactive Server nos Blazor render modes

Enquanto o SSR e o Streaming Rendering são excelentes para exibir informações, ainda precisamos de interação.

Sempre que queremos dar vida aos componentes, com botões, inputs e outros elementos, temos três opções nos Blazor render modes. A primeira é o Interactive Server.

Nesse formato, adicionamos a diretiva @rendermode InteractiveServer ao componente ou à página.

O comportamento lembra o do Streaming Rendering. Ou seja, o componente aparece na tela, mas continua conectado ao servidor.

Componente Counter rodando com o modo Interactive Server dos Blazor render modes no navegador
Counter no modo Interactive Server.

Dessa forma, o código roda no servidor, que usa o SignalR sobre WebSocket para manter a conexão aberta.

Ferramentas do navegador exibindo as mensagens trocadas pelo WebSocket do SignalR durante a interação com o Counter
Mensagens trafegando pelo WebSocket.

O componente responde e se atualiza quase instantaneamente. Isso acontece porque a conexão via socket e a otimização na transferência de dados ajudam bastante.

Porém, assim como no Streaming Rendering, modificamos os elementos depois que o HTML chega ao navegador. Por isso, o código fonte da página não muda.

Código fonte do componente Counter mostrando o valor inicial mesmo após vários cliques no modo Interactive Server
HTML com o valor inicial.

Além disso, os modos interativos usam prerendering por padrão. Assim, o servidor entrega uma versão estática primeiro e, depois, ativa a interatividade.

Criando projetos no Visual Studio

Quando escrevi a primeira versão deste conteúdo, a versão mais recente do .NET 8 era a RC 2. Por isso, criamos os projetos pelo Visual Studio Preview.

Hoje, o Visual Studio 2022 versão 17.8 ou superior já traz o template. Além disso, o comando dotnet new blazor aceita a opção --interactivity com os valores None, Server, WebAssembly e Auto.

# cria um Blazor Web App com interatividade Auto
dotnet new blazor -o BlazorAppAuto --interactivity Auto

No Visual Studio, selecione a opção File, New Project e busque pelo template Blazor Web App.

Tela de novo projeto do Visual Studio com o template Blazor Web App selecionado
Template Blazor Web App.
Tela de configuração do nome e da localização do novo projeto Blazor no Visual Studio
Nome e local do projeto.

Em seguida, a opção Interactivity Type define o tipo de interação.

Também temos o Interactivity Location, que pode ser Global, para todas as páginas e componentes, ou Per page/component, para cada página ou componente.

Opções Interactivity Type e Interactivity Location que configuram os Blazor render modes no Visual Studio
Tipo e local da interatividade.

Vale ressaltar que o Identity também chegou ao Blazor. Assim, podemos configurar a autenticação como None ou Individual Accounts.

Opção de tipo de autenticação do Blazor Web App com os valores None e Individual Accounts
Opções de autenticação.

Por fim, temos os tipos de interação. Por enquanto, vamos ignorar None e Server, que já vimos, e focar em WebAssembly e Auto.

Lista de Interactivity Type com as opções None, Server, WebAssembly e Auto dos Blazor render modes
Opções de interatividade.

Nos próximos tópicos, usamos como base projetos criados com Interactivity Type WebAssembly e Auto, respectivamente.

Interactive WebAssembly

Mas, se podemos rodar o .NET direto no navegador com WebAssembly, por que usar o modo anterior?

Por exemplo, não daria para guardar uma connection string no WASM, porque ela ficaria exposta no navegador.

Por outro lado, entre os Blazor render modes, o Interactive Server exige mais infraestrutura e pode ser mais lento.

Além de não depender do servidor, o WASM aproveita a capacidade da máquina do cliente. Se quiser se aprofundar, leia sobre WASM e o futuro da web.

Inspecionando o projeto WebAssembly

Para iniciar um projeto com Interactivity Type em WebAssembly, basta selecionar a opção conforme a imagem abaixo.

Opção WebAssembly selecionada no Interactivity Type do template Blazor Web App
Escolhendo o WebAssembly.

Com esses Blazor render modes, o template cria dois projetos: servidor e cliente.

Solution Explorer com os projetos BlazorApp1 e BlazorApp1.Client criados pelo modo WebAssembly dos Blazor render modes
Projetos servidor e Client.

No Program.cs do servidor, BlazorApp1, aparece o AddInteractiveWebAssemblyComponents.

Esse item só aparece em projetos com Interactivity Type definido como WebAssembly ou Auto.

Além disso, como o WebAssembly roda apenas no navegador, precisamos de um projeto dedicado para ele, o BlazorApp1.Client.

// ...
builder
    .Services
    .AddRazorComponents()
    .AddInteractiveWebAssemblyComponents();

// ...
app.MapRazorComponents<App>()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(Counter).Assembly);

Já o AddAdditionalAssemblies(typeof(Counter).Assembly) indica onde encontrar os componentes do tipo WebAssembly.

Notamos também que, na pasta Components/Pages do projeto BlazorApp1, o componente Counter.razor não existe mais.

Pasta Pages do projeto servidor BlazorApp1 sem o componente Counter
Pages do servidor sem o Counter.

Essa mudança acontece porque, em um projeto com suporte a WebAssembly, o template move o componente de exemplo para o BlazorApp1.Client.

Todos os componentes desse projeto podem rodar em WebAssembly. Ainda assim, o projeto servidor também pode usá-los.

O Program.cs do BlazorApp1.Client é extremamente simples.

using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

await builder.Build().RunAsync();

Por fim, na pasta Pages do Client, encontramos o Counter.razor, cujo código só muda na diretiva de render mode.

Pasta Pages do projeto BlazorApp1.Client contendo o componente Counter.razor
Counter no projeto Client.
@page "/counter"
@rendermode InteractiveWebAssembly

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Desta vez, como mostra a segunda linha, usamos InteractiveWebAssembly no lugar do InteractiveServer que aplicamos antes.

Executando o projeto WebAssembly

O projeto BlazorApp1.Client serve apenas como local para guardar as páginas e os componentes que queremos rodar em WebAssembly.

Por isso, sempre executamos o projeto BlazorApp1, que carrega o BlazorApp1.Client consigo.

O mesmo vale para a publicação. Ou seja, fazemos o deploy do BlazorApp1, que é a aplicação, e não do BlazorApp1.Client.

Visual Studio com o projeto BlazorApp1 definido como projeto de inicialização
Executando o projeto servidor.

Como resultado, temos o mesmo exemplo dos tópicos anteriores. Porém, agora o Counter.razor roda via WebAssembly, com C# executando no navegador.

Componente Counter funcionando no navegador com o modo Interactive WebAssembly dos Blazor render modes
Counter rodando em WebAssembly.

No primeiro carregamento, notamos uma leve lentidão, porque o navegador baixa o runtime do .NET.

Auto nos Blazor render modes

Finalizando os Blazor render modes, temos o Auto, que mescla Server e WebAssembly.

Nesse modo, o componente começa no modelo Server, já que essa é a forma mais rápida de exibi-lo.

Dizemos isso porque, no WASM, o navegador precisa baixar o .NET. Esse download pode levar algum tempo, dependendo da conexão do usuário.

Depois que o componente aparece na tela, o Blazor inicia o download do runtime do .NET em segundo plano.

Nas próximas visitas, o Blazor verifica se o runtime já está no navegador. Em caso positivo, o componente roda em WebAssembly, o que o torna mais performático.

Um detalhe importante: o Auto nunca troca o modo de um componente que já está na página. Ele decide uma vez e mantém essa escolha enquanto o componente existir.

Em resumo, no Auto temos o melhor dos dois mundos, com agilidade na primeira renderização e o desempenho do WASM depois.

Inspecionando o projeto Auto

Para iniciar um projeto Auto, selecione a opção abaixo.

Opção Auto selecionada no Interactivity Type para combinar os Blazor render modes Server e WebAssembly
Escolhendo o modo Auto.

Esse projeto combina dois Blazor render modes, Server e WASM.

Solution Explorer com os projetos BlazorAppAuto e BlazorAppAuto.Client
Solução com o projeto Client.

Desta vez, porém, o Program.cs usa tanto o AddInteractiveServerComponents quanto o AddInteractiveWebAssemblyComponents.

// ...

builder
    .Services
    .AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

// ...

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(Counter).Assembly);

Da mesma forma, precisamos do AddInteractiveServerRenderMode e do AddInteractiveWebAssemblyRenderMode para suportar os dois modos.

Executando o projeto Auto

A execução acontece pelo projeto BlazorAppAuto, e não pelo BlazorAppAuto.Client, igual ao exemplo anterior.

Visual Studio executando o projeto BlazorAppAuto como projeto de inicialização
Executando o BlazorAppAuto.

Como resultado, temos um comportamento idêntico ao anterior, com o componente Counter na tela.

Componente Counter exibido no navegador com o modo Interactive Auto dos Blazor render modes
Counter no modo Auto.

Porém, existe um detalhe sutil. Na primeira execução, o componente rodou no modo Server.

Enquanto o usuário navegava pela aplicação, o navegador baixou o runtime do .NET em segundo plano.

Agora, sempre que o usuário voltar ao Counter, o componente roda em WASM, já que o runtime está no navegador.

Ao analisar o Counter.razor, vemos a diretiva InteractiveAuto no lugar de InteractiveServer ou InteractiveWebAssembly.

@page "/counter"
@rendermode InteractiveAuto

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Como escolher entre os Blazor render modes

Em resumo, temos praticamente o mesmo código em todos os modos. No entanto, cada um se comporta de forma bem diferente por baixo dos panos.

Para páginas de conteúdo que dependem de SEO, o Static SSR é a escolha natural. Afinal, o HTML completo sai do servidor.

Quando a página carrega dados lentos e você quer mostrar o layout rápido, o Streaming Rendering melhora a experiência do usuário.

Se o componente precisa de interação e acesso seguro a recursos do servidor, o Interactive Server resolve sem expor segredos.

Já o Interactive WebAssembly faz sentido quando você quer aliviar o servidor e usar o poder de processamento do cliente.

Por fim, o Auto equilibra velocidade inicial e desempenho posterior. Para entender melhor a base do framework, veja também uma visão geral do Blazor.

Conclusão sobre Blazor render modes

Os Blazor render modes eliminaram o principal argumento contra o Blazor em sites públicos, que era a falta de SSR.

Com eles, uma mesma aplicação pode ter páginas estáticas para SEO, áreas de compra interativas e um Player rodando no navegador.

Portanto, aquele cenário do balta, que exigiria três aplicações, hoje cabe em um único Blazor Web App. Experimente combinar os modos e escolha o melhor para cada componente.

FAQ

Qual é o render mode padrão de um componente no Blazor Web App?

Quando o componente não declara nenhuma diretiva @rendermode, ele usa o Static SSR. Ou seja, o servidor gera o HTML e não existe interatividade.

O Streaming Rendering ajuda no SEO?

Só parcialmente, porque o HTML inicial contém apenas o conteúdo provisório. Se os dados carregados depois forem importantes para o SEO, prefira o SSR sem streaming.

Onde ficam os componentes que usam WebAssembly ou Auto?

Eles ficam no projeto que termina com .Client. O projeto servidor referencia esse assembly com AddAdditionalAssemblies.

O modo Auto troca de Server para WebAssembly com o componente aberto?

Não, ele decide o modo uma única vez para cada componente. A troca para WebAssembly só acontece em renderizações futuras, depois que o runtime estiver em cache.

Posso usar render modes no Blazor Server ou Blazor WebAssembly antigos?

Não, os render modes pertencem aos Blazor Web Apps a partir do .NET 8. Os modelos de hospedagem anteriores continuam funcionando com seus próprios conceitos.