SSR vs CSR: Quando Cada Abordagem Faz Sentido (e Quando Não Faz)
O erro mais caro: escolher renderização por preferência pessoal
A maioria dos projetos não falha por usar SSR ou CSR. Falha por usar a abordagem errada para o tipo de página errado. Um dashboard interno renderizado no servidor desperdiça CPU em HTML que ninguém indexa. Uma landing page renderizada inteiramente no cliente entrega uma tela branca por 2-3 segundos enquanto o JavaScript carrega, parseia e executa.
A pergunta certa nunca é "SSR ou CSR?". É: quem precisa ver esse conteúdo primeiro, o crawler ou o usuário logado? E, logo depois: esse conteúdo muda a cada request ou a cada deploy?
Como SSR funciona por baixo
No Server-Side Rendering, o servidor executa o componente React (ou equivalente), gera HTML completo e envia ao navegador. O browser renderiza o HTML imediatamente. Depois, o JavaScript do cliente faz a hidratação: conecta event listeners ao HTML já visível.
// app/products/[slug]/page.tsx
// Next.js App Router: Server Component por padrão
// O fetch roda no servidor, nunca no navegador do usuário
import { notFound } from "next/navigation";
interface Product {
id: string;
name: string;
price: number;
description: string;
}
async function getProduct(slug: string): Promise<Product | null> {
const res = await fetch(`https://api.store.com/products/${slug}`, {
// next.revalidate define cache incremental no servidor
// 60s significa: o HTML é servido do cache por 1 minuto,
// depois revalida em background (ISR)
next: { revalidate: 60 },
});
if (!res.ok) return null;
return res.json();
}
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const product = await getProduct(slug);
if (!product) notFound();
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span>R$ {product.price.toFixed(2)}</span>
</main>
);
}
O ponto que importa: o crawler do Google recebe HTML pronto. O Time to First Contentful Paint (FCP) depende da latência do servidor, não do tamanho do bundle JavaScript.
Como CSR funciona por baixo
No Client-Side Rendering, o servidor entrega um HTML mínimo (geralmente um <div id="root">) e um bundle JS. O navegador baixa, parseia e executa o JavaScript. Só então o conteúdo aparece.
// src/pages/Dashboard.tsx
// SPA clássica com React Router e fetch no cliente
import { useEffect, useState } from "react";
interface Metric {
label: string;
value: number;
}
export function Dashboard() {
const [metrics, setMetrics] = useState<Metric[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// fetch roda no navegador do usuário
// o servidor nunca vê essa requisição
fetch("/api/dashboard/metrics", {
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
})
.then((res) => res.json())
.then((data) => setMetrics(data))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Carregando métricas...</p>;
return (
<ul>
{metrics.map((m) => (
<li key={m.label}>
{m.label}: {m.value}
</li>
))}
</ul>
);
}
Aqui o HTML inicial é vazio. O Google até consegue executar JavaScript para indexação, mas com atraso e sem garantias. Para um dashboard atrás de login, isso não importa. Para uma página de produto, é um problema real de SEO.
Matriz de decisão: quando usar o quê
| Critério | SSR | CSR | SSG/ISR |
|---|---|---|---|
| SEO crítico (e-commerce, blog, landing page) | Sim | Não | Sim |
| Conteúdo muda a cada request (preço em tempo real, estoque) | Sim | Sim (via API) | Não (stale até rebuild) |
| Conteúdo muda a cada deploy (documentação, blog) | Desnecessário | Desnecessário | Sim (melhor opção) |
| Aplicação atrás de login (dashboard, admin) | Desperdício de CPU | Sim | Não |
| First Contentful Paint abaixo de 1s | Sim (se servidor rápido) | Difícil (depende do |
Leia o artigo completo em https://www.vivodecodigo.com.br/nextjs/ssr-vs-csr-quando-usar-server-side-client-side-rendering-1781265716094