Capítulo 18: Angular SSR, Prerendering e Hidratação Não Destrutiva
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:
- Integração Nativa com o Angular CLI: O comando
ng new meu-app --ssrconfigura um servidor Node.js otimizado pronto para produção. - 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).
- 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
HttpClientintercepta 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
- 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 hookafterNextRender().
- 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.
- Se o servidor gerar
🛡️ 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
}