-3

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érioSSRCSRSSG/ISR
SEO crítico (e-commerce, blog, landing page)SimNãoSim
Conteúdo muda a cada request (preço em tempo real, estoque)SimSim (via API)Não (stale até rebuild)
Conteúdo muda a cada deploy (documentação, blog)DesnecessárioDesnecessárioSim (melhor opção)
Aplicação atrás de login (dashboard, admin)Desperdício de CPUSimNão
First Contentful Paint abaixo de 1sSim (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

Carregando publicação patrocinada...