Desbravando o SSR no Angular 17

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 "Server Side Rendering". À esquerda, há um ícone representando um servidor, e à direita, um ícone de um monitor de computador com o logo do Angular. Uma seta verde conecta o servidor ao computador.

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

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 "localhost:4200" aberta. À esquerda, há um botão grande com o texto "increment" e abaixo dele, o número "0". À direita, está aberta a ferramenta de desenvolvedor do Chrome na aba "Elements". Na seção "Styles", o estilo do elemento "body" está selecionado, exibindo as propriedades "display: block;" e "margin: 8px;". Na parte inferior, a aba "Console" exibe várias mensagens, incluindo "Resultado é gerado tanto no servidor quanto no navegador." e "Angular is running in development mode.

E no terminal, vamos ter o seguinte resultado:

A imagem mostra a saída de um terminal após a execução do comando "ng serve" para um projeto Angular. Exibe detalhes sobre os arquivos gerados e seus tamanhos. Mostra uma URL local "http://localhost:4200/" para acessar a aplicação e mensagens indicando que o "Resultado é gerado tanto no servidor quanto no navegador.

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 "localhost:4200" aberta. À esquerda, há um botão grande com o texto "Increment Button" e abaixo dele, o número "0". À direita, a ferramenta de desenvolvedor do Chrome está aberta na aba "Elements", mostrando o estilo do elemento "body" com as propriedades "display: block;" e "margin: 8px;". Na parte inferior, a aba "Console" exibe mensagens indicando que "Resultado é gerado apenas no navegador, não no servidor." e "Angular is running in development mode."

E no terminal:

A imagem mostra a saída de um terminal após a execução do comando "ng serve" para um projeto Angular. Exibe detalhes sobre os arquivos gerados e seus tamanhos, incluindo arquivos de chunk inicial e lazy para o servidor. Mostra uma URL local "http://localhost:4200/" para acessar a aplicação e mensagens indicando que o "Resultado é gerado tanto no servidor quanto no navegador."

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 é engenheiro de software, envolvido na arquitetura, design e implementação de microsserviços, micro frontends e sistemas distribuídos. Tem experiência significativas em aplicativos, integração e arquitetura corporativa. É Engenheiro de Software pela UNESA e Arquiteto de Software pela PUC Minas.

Veja outros artigos sobre Front-end