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!

A imagem mostra um gráfico ilustrando o conceito de

Sabendo isso, vamos construir uma aplicação que utilize SSR, proporcionando uma experiência de usuário mais fluida e rápida.

Banner promocional da Alura, com um design futurista em tons de azul, apresentando o texto "Já sabe quais os próximos passos para seus estudos em Front-end? O Guia de Carreira em Front-end vai te ajudar nisso". À direita, está a foto de um jovem sorridente olhando para a esquerda do banner, ao lado de uma lista de tópicos oferecidos pelo guia, como "Principais cursos e formações da Alura nessa área", "Caminhos para carreira Front-end Angular e React", "Como migrar do Front-end para Back-end" e "Dicas de especialistas na área". No canto inferior direito, há um ícone de download e o texto "Baixe gratuitamente".

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:

A imagem mostra a interface de um navegador web, especificamente o Google Chrome, com a página

E no terminal, vamos ter o seguinte resultado:

A imagem mostra a saída de um terminal após a execução do comando

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:

A imagem mostra um navegador web com a página

E no terminal:

A imagem mostra a saída de um terminal após a execução do comando

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!

Vinicios Neves
Vinicios Neves

Vinicios Neves, Tech Lead e Educador, mistura código e didática há mais de uma década. Especialista em TypeScript, lidera equipes full-stack em Portugal e inspira futuros desenvolvedores na FIAP e Alura. Com um pé no código e outro no ensino, ele prova que a verdadeira engenharia de software vai além das linhas de código. Além de, claro, ser senior em falar que depende.

Veja outros artigos sobre Front-end