Capítulo 18: Angular SSR, Prerendering e Hidratação Não Destrutiva

Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR


🗺️ Mapa Conceitual do Fluxo de SSR e Hidratação Não Destrutiva

flowchart TD
    User["Usuário / Web Crawler"] -->|"1. Requisição HTTP Inicial (GET /)"| ServerNode["Servidor Node.js (@angular/ssr)"]

    subgraph SSR_EXECUTION["Execução no Servidor (Node.js)"]
        S1["Compilação de Componentes & Execução de Rotas"]
        S2["TransferState: Cache de Respostas HTTP da API"]
        S3["Geração do HTML Inicial Completo com CSS Embutido"]
    end

    ServerNode --> SSR_EXECUTION
    SSR_EXECUTION -->|"2. Envio de HTML Pronto (FCP Ultra-Rápido)"| Browser["Navegador do Usuário"]

    subgraph CLIENT_HYDRATION["Hidratação no Cliente (Browser)"]
        H1["Download dos Chunks JS em Segundo Plano"]
        H2["provideClientHydration(): Reutiliza o DOM Existente (Sem Flickeio)"]
        H3["withEventReplay(): Reproduz Cliques do Usuário Ocorridos Pré-Hidratação"]
    end

    Browser --> CLIENT_HYDRATION
    CLIENT_HYDRATION --> FullSPA["Aplicação 100% Interativa e Viva (SPA)"]

    style User fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style ServerNode fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style SSR_EXECUTION fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style CLIENT_HYDRATION fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. A Nova Era do SSR no Angular 18+

Durante muitos anos, o Server-Side Rendering (SSR) no Angular (antigo Angular Universal) foi considerado complexo e propenso a inconsistências visuais, como o temido flickering (onde o DOM gerado no servidor era destruído e reconstruído do zero no browser ao carregar o JavaScript).

A partir do Angular 17 e consolidado no Angular 18/19 com o pacote oficial @angular/ssr:

  1. Integração Nativa com o Angular CLI: O comando ng new meu-app --ssr configura um servidor Node.js otimizado pronto para produção.
  2. Hidratação Não Destrutiva (Non-Destructive Hydration): O Angular não destrói o DOM renderizado pelo servidor. Ele preserva cada nó HTML e simplesmente anexa os listeners de eventos (event listeners).
  3. Event Replay Nativo: Se o usuário clicar em um botão antes do JavaScript terminar de baixar, o Angular grava o clique e o reproduz imediatamente após a hidratação, evitando cliques perdidos.

⚙️ 2. Configurando a Hidratação no app.config.ts

A ativação da hidratação com suporte a reprodução de eventos é declarada no arquivo de configuração do cliente:

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideClientHydration, withEventReplay, withHttpTransferCacheOptions } from '@angular/platform-browser';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withFetch()), // withFetch é mandatório para SSR de alto desempenho
    provideClientHydration(
      withEventReplay(), // Grava cliques do usuário antes do bundle JS carregar e os executa
      withHttpTransferCacheOptions({
        includePostRequests: false // Evita cache acidental de mutações POST
      })
    )
  ]
};

🔄 3. TransferState: Evitando Requisições HTTP Duplicadas

Um dos maiores problemas no SSR ingênuo é a duplicação de chamadas de rede: a aplicação faz um GET /api/produtos no servidor para renderizar o HTML inicial, e quando o navegador carrega o JavaScript no cliente, a mesma chamada HTTP é disparada uma segunda vez!

O Angular resolve isso automaticamente através do HTTP Transfer Cache:

  • As respostas HTTP obtidas no servidor são serializadas e injetadas dentro de uma tag <script id="ng-state"> no HTML final.
  • No navegador, o HttpClient intercepta a chamada, encontra os dados no cache de transferência e entrega a resposta imediatamente sem abrir conexão com a internet.

🛡️ 4. Isolando Códigos Específicos de Browser e Servidor

No servidor Node.js não existem as variáveis globais window, document, navigator ou localStorage. Executar código que tenta acessá-las diretamente causará um erro fatal no servidor.

Para proteger seu código, utilize os utilitários isPlatformBrowser e isPlatformServer:

import { Component, inject, PLATFORM_ID, signal } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Component({
  selector: 'app-detector-ambiente',
  standalone: true,
  template: `
    <div class="card">
      <p>Ambiente de Execução: <strong>{{ ambiente() }}</strong></p>
      <p>Tema Salvo: {{ temaLocal() }}</p>
    </div>
  `
})
export class DetectorAmbienteComponent {
  private platformId = inject(PLATFORM_ID);

  ambiente = signal<'SERVIDOR (Node.js)' | 'CLIENTE (Navegador)'>('SERVIDOR (Node.js)');
  temaLocal = signal<string>('Padrão');

  constructor() {
    if (isPlatformBrowser(this.platformId)) {
      // Este bloco executa com 100% de segurança APENAS no navegador
      this.ambiente.set('CLIENTE (Navegador)');
      const temaSalvo = localStorage.getItem('app-theme') || 'dark';
      this.temaLocal.set(temaSalvo);
    } else if (isPlatformServer(this.platformId)) {
      // Este bloco executa com 100% de segurança APENAS no servidor
      console.log('Gerando página estática para SEO no servidor...');
    }
  }
}

⚖️ 5. Comparativo: CSR vs SSR vs Prerendering (SSG)

Modalidade Onde Renderiza First Contentful Paint (FCP) SEO & Crawlers Custo de Servidor
Client-Side Rendering (CSR) Navegador Mais lento (espera download do JS) Médio Praticamente zero (CDN estático)
Server-Side Rendering (SSR) Servidor Node.js em tempo real Instantâneo Excelente Médio (computação por request)
Prerendering (SSG estático) Build time (ng build) Instantâneo Excelente Zero (hospedado em CDN como GitHub Pages)

⚠️ 6. Armadilhas Comuns e Anti-Patterns

  1. Manipulação Direta do DOM com document.getElementById():
    • Quebra a execução no servidor e destrói o modelo de hidratação.
    • Correção: Use template refs com viewChild() e execute manipulações de DOM apenas dentro do hook afterNextRender().
  2. Depender de Horários do Servidor que Geram Conflitos de Hidratação:
    • Se o servidor gerar new Date() em UTC e o navegador interpretar em GMT-3, haverá uma discrepância de texto que o Angular acusará no console como Hydration Mismatch.
    • Correção: Utilize formatações de data com fuso horário fixo ou aguarde a hidratação no cliente.

🛡️ 7. Boas Práticas para Produção

  • Ative withEventReplay() Sempre: Garante a melhor experiência de usuário em conexões 3G/4G instáveis, evitando que cliques em menus ou botões sejam ignorados antes do fim do download da aplicação.
  • Utilize SSG para Páginas de Conteúdo: Para blogs, portais didáticos ou documentações que não mudam a cada segundo, utilize o prerender do Angular CLI para gerar HTML puro durante a compilação.

📋 8. Cheat-Sheet: Resumo Executivo

// app.config.ts
provideClientHydration(withEventReplay())

// Checagem de plataforma:
const platformId = inject(PLATFORM_ID);
if (isPlatformBrowser(platformId)) {
  // Acesso seguro a window / localStorage
}