💻 Exemplos: Capítulo 18: Angular Server-Side Rendering (SSR) e Hydration
Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 18 — SSR e Hidratação Não-Destrutiva com provideClientHydration
Este exemplo apresenta a configuração corporativa de Server-Side Rendering (SSR) no Angular 18+, utilizando provideClientHydration(withEventReplay()) para hidratação não-destrutiva sem piscar a tela e proteção de código de navegador com isPlatformBrowser.
1. Configuração de Hidratação: src/app/app.config.ts
// src/app/app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideClientHydration, withEventReplay, withHttpTransferCacheOptions } from '@angular/platform-browser';
import { routes } from './app.routes';
import { provideHttpClient, withFetch } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withFetch()),
// 1. Hidratação Não-Destrutiva com Replay de Eventos do Usuário
// Se o usuário clicar em um botão antes do JS carregar, o evento é gravado e reexecutado pós-hidratação!
provideClientHydration(
withEventReplay(),
withHttpTransferCacheOptions({
includePostRequests: false
})
)
]
};
2. Componente com Checagem Isomórfica: src/app/components/ssr-safe-widget.component.ts
// src/app/components/ssr-safe-widget.component.ts
import { Component, inject, PLATFORM_ID, signal, OnInit } from '@angular/core';
import { isPlatformBrowser, isPlatformServer, CommonModule } from '@angular/common';
interface ServerRenderStats {
environment: string;
renderTimestamp: string;
viewportDimensions?: string;
}
@Component({
selector: 'app-ssr-safe-widget',
standalone: true,
imports: [CommonModule],
template: `
<div class="max-w-xl mx-auto p-6 bg-white border border-slate-200 rounded-2xl shadow-sm space-y-4">
<div class="border-b pb-3">
<h2 class="text-xl font-bold text-slate-800">⚡ Angular SSR & Hydration Universal</h2>
<p class="text-xs text-slate-500">withEventReplay() e TransferState de dados HTTP</p>
</div>
<div class="p-4 bg-slate-50 border rounded-xl space-y-2 text-xs">
<div class="flex justify-between">
<span class="text-slate-500 font-semibold">Executando em:</span>
<span
class="px-2 py-0.5 rounded-full font-bold uppercase"
[class.bg-purple-100]="isBrowser()"
[class.text-purple-800]="isBrowser()"
[class.bg-blue-100]="!isBrowser()"
[class.text-blue-800]="!isBrowser()"
>
{{ isBrowser() ? 'Navegador (Cliente)' : 'Node.js (Servidor SSR)' }}
</span>
</div>
<div class="flex justify-between">
<span class="text-slate-500 font-semibold">Timestamp de Renderização:</span>
<span class="font-mono text-slate-800">{{ stats().renderTimestamp }}</span>
</div>
@if (isBrowser()) {
<div class="flex justify-between pt-1 border-t text-emerald-700 font-semibold">
<span>Dimensões do Viewport (DOM):</span>
<span class="font-mono">{{ stats().viewportDimensions }}</span>
</div>
}
</div>
<button
type="button"
(click)="handleEarlyClick()"
class="w-full py-2.5 bg-red-600 hover:bg-red-700 text-white rounded-xl text-xs font-bold transition-colors"
>
Testar Event Replay (Clique durante Carga)
</button>
@if (clickConfirmed()) {
<div class="p-3 bg-emerald-50 text-emerald-800 rounded-lg text-xs font-medium">
✓ Evento de clique capturado e processado com sucesso pós-hidratação!
</div>
}
</div>
`
})
export class SsrSafeWidgetComponent implements OnInit {
private platformId = inject(PLATFORM_ID);
isBrowser = signal<boolean>(isPlatformBrowser(this.platformId));
clickConfirmed = signal<boolean>(false);
stats = signal<ServerRenderStats>({
environment: isPlatformServer(this.platformId) ? 'Server' : 'Browser',
renderTimestamp: new Date().toISOString()
});
ngOnInit(): void {
// Código seguro: só acessa o objeto window se estiver comprovadamente no navegador!
if (isPlatformBrowser(this.platformId)) {
this.stats.update((curr) => ({
...curr,
viewportDimensions: `${window.innerWidth} x ${window.innerHeight} px`
}));
}
}
handleEarlyClick(): void {
this.clickConfirmed.set(true);
}
}
🖥️ Saída / Comportamento Esperado
HTML Enviado pelo Servidor Node (Visualizado via View-Source):
<app-ssr-safe-widget>
...
<span>Node.js (Servidor SSR)</span>
...
</app-ssr-safe-widget>
No Navegador (Hydration):
O Angular 18 reconcilia o DOM sem recriar os nós HTML.
isBrowser() torna-se true.
O badge atualiza para "Navegador (Cliente)".
As dimensões do Viewport ("1920 x 1080 px") são preenchidas sem nenhum hydration error.
Caso o usuário clique no botão antes do bundle JavaScript terminar de baixar:
O withEventReplay() grava o evento de clique na fila interna e o reproduz imediatamente após a hidratação, ativando a mensagem de confirmação.
🔍 Destaques da Implementação
provideClientHydration(withEventReplay()): Elimina a frustração de “botões mortos” em conexões 3G/4G durante o carregamento inicial.isPlatformBrowser(this.platformId): A forma canônica e obrigatória de blindar APIs do browser (window,localStorage,navigator) contra falhas no SSR.- TransferState Embutido: O
HttpClientserializa as respostas do servidor e as reidrata no cliente sem disparar requisições duplicadas.