Desbravando o SSR no Angular 17
Você já se perguntou por que em alguns cenários queremos que o HTML seja gerado no lado do servidor, mesmo em aplicações que usam Angular?
Neste artigo, vou te mostrar como implementar SSR em uma aplicação Angular. Vamos falar sobre:
- Os comandos mágicos para criar uma aplicação SSR do zero ou adicionar SSR a uma aplicação existente;
- Quais partes do Angular rodam no servidor e quais rodam no cliente;
- Como garantir que componentes específicos funcionem corretamente, tanto no servidor quanto no navegador;
- Dicas e truques para lidar com objetos globais do navegador que não existem no servidor (adeus, erros chatos com window e document!).
Então, prepare-se para uma viagem pelo mundo da Server Side Rendering, ou em português, Renderização do Lado do Servidor (SSR)!
O que é o SSR
O SSR é uma técnica poderosa que nos permite renderizar páginas diretamente no servidor, gerando um HTML completo antes mesmo que a página chegue ao navegador do usuário.
Mas por que isso é tão importante? Bem, imagine a diferença: ao invés do usuário esperar o JavaScript carregar e montar a página, ele já recebe tudo prontinho, como uma pizza saindo do forno direto para a mesa!
Sabendo isso, vamos construir uma aplicação que utilize SSR, proporcionando uma experiência de usuário mais fluida e rápida.
Construindo uma aplicação com SSR
Para iniciar sua aventura com uma nova aplicação Angular usando SSR, execute o seguinte comando na interface de linha de comando (CLI):
ng new app-com-ssr --ssr
Mas se você já tem uma aplicação Angular e quer adicionar SSR a ela, basta utilizar este comando mágico:
ng add @angular/ssr
Componentes e processos: quem opera onde?
Durante a renderização, o construtor e os hooks do ciclo de vida inicial (excluindo afterRender e afterNextRender) são executados tanto no servidor quanto no navegador. Métodos como ngOnChange
, ngOnInit
e ngDoCheck
rodam em ambos, mas outros manipuladores de eventos funcionam apenas no navegador.
Vamos analisar um exemplo prático para entender como isso funciona:
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule , RouterOutlet, FormsModule],
template: `<button (click)="buttonClick()">increment</button>
<br>
{{i}}`,
styles: []
})
export class AppComponent implements OnInit {
i = 0;
ngOnInit(): void {
console.log("Resultado é gerado tanto no servidor quanto no navegador.");
}
constructor() {
console.log("Resultado é gerado tanto no servidor quanto no navegador.");
}
buttonClick() {
console.log("Resultado é gerado apenas no navegador, não no servidor.");
this.i++;
}
}
Ao executar o código acima, no navegador, vamos ter este resultado:
E no terminal, vamos ter o seguinte resultado:
Durante o ngOnInit, objetos globais do navegador, como window, document, navigator, location ou localStorage não podem ser utilizados, pois eles não estão disponíveis no ambiente do servidor.
Por exemplo, se utilizarmos console.log dentro do construtor:
constructor() {
console.log("Construtor: Resultado é gerado tanto no servidor quanto no navegador.");
}
Ou no hook do ciclo de vida ngOnInit:
ngOnInit(): void {
console.log("ngOnInit: Resultado é gerado tanto no servidor quanto no navegador.");
}
O resultado será:
O console.log
aparecerá tanto na linha de comando (CLI) quanto no console do navegador, assim como a famosa frase “Você não passará!” ecoa por toda a Terra Média.
No entanto, se tentarmos usar localStorage
dentro do construtor vamos tomar um erro porque a localStorage
não está disponível no servidor.
Como lidar com objetos globais do navegador?
Podemos usar hooks de ciclo de vida como afterRender
e afterNextRender
para ativar funcionalidades específicas do navegador:
import { Component, afterRender } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, FormsModule],
template: `<button (click)="buttonClick()">Increment Button</button>
<br>
{{i}}`,
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'demo';
i = 0;
constructor() {
afterRender(() => {
// Isso roda apenas no cliente / navegador
console.log("Construtor: Resultado é gerado tanto no servidor quanto no navegador.");
});
}
ngOnInit(): void {
console.log("ngOnInit: Resultado é gerado tanto no servidor quanto no navegador.");
}
buttonClick() {
console.log("Botão: Resultado é gerado apenas no navegador, não no servidor.");
this.i++;
}
}
Utilizando PLATFORM_ID
com isPlatformBrowser
e isPlatformServer
, podemos obter e manipular funcionalidades específicas do navegador:
import { Component, Inject, PLATFORM_ID } from '@angular/core';
import { CommonModule, isPlatformBrowser, isPlatformServer } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, FormsModule],
template: `<button (click)="buttonClick()">Increment Button</button>
<br>
{{i}}`,
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'demo';
i = 0;
constructor(@Inject(PLATFORM_ID) private platformId: Object) {
if (isPlatformBrowser(platformId)) {
console.log("Resultado é gerado apenas no navegador, não no servidor.");
}
if (isPlatformServer(platformId)) {
console.log("Resultado é gerado apenas no servidor, não no navegador.");
}
}
ngOnInit(): void {
if (isPlatformBrowser(this.platformId)) {
console.log("Resultado é gerado apenas no navegador, não no servidor.");
}
if (isPlatformServer(this.platformId)) {
console.log("Resultado é gerado apenas no servidor, não no navegador.");
}
}
buttonClick() {
this.i++;
}
}
Agora, ao tratar o ambiente que estamos, -cliente ou servidor-, podemos ver as saídas específicas de cada lado. Abaixo podemos ver o resultado no console do navegador:
E no terminal:
Conclusão e próximos passos
Você chegou ao fim desta jornada sobre a renderização no lado do servidor (SSR) com Angular.
Agora você entende como essa técnica pode melhorar significativamente a performance, SEO e experiência do usuário das suas aplicações.
Implementamos SSR do zero, adicionamos a projetos existentes e vimos como gerenciar componentes tanto no servidor quanto no cliente.
Com isso, você está pronto para aplicar esse conhecimento em seus próprios projetos e ver os benefícios em ação.
- Prática: aplique SSR em um projeto real para observar os ganhos de performance e SEO;
- Aprofundamento: continue estudando e explorando mais sobre SSR e outras técnicas avançadas do Angular, com a formação Desenvolva Aplicações Escaláveis com Angular;
- Comunidade: participe de fóruns e comunidades, compartilhe suas experiências e aprenda com outros desenvolvedores.
Esperamos que este conteúdo tenha sido útil e que você se sinta mais confiante para utilizar SSR em suas aplicações.
Continue explorando e aprimorando suas habilidades, pois o mundo do desenvolvimento está sempre em evolução.
Vida longa e próspera!