- O problema real da escolha errada
- CSR: o ponto de partida que ninguém menciona
- SSG: quando o conteúdo não muda (ou quase não muda)
- SSR: dados frescos, custo real
- ISR: o meio-termo que parece mágica
- Comparação direta: qual usar em cada cenário
- Erros comuns que aparecem em code review
- FAQ
- Próximos passos
O problema real da escolha errada
Você jogou SSR em tudo porque "precisa ser dinâmico". O resultado: Cold Start no Vercel todo request, TTFB de 800ms numa página de produto que muda três vezes por semana. Ou fez SSG em tudo porque "é mais rápido" e agora o usuário vê o estoque como "disponível" de um produto que esgotou há duas horas.
As três estratégias de renderização do Next.js resolvem problemas diferentes. Confundir qual usar não é só um problema de performance — é um problema de produto.
💡 Dica: A pergunta certa não é "qual é mais rápido?". É "com que frequência esse dado muda e quem precisa da versão mais recente?"
CSR: o ponto de partida que ninguém menciona
Antes de falar de SSR/SSG/ISR, vale lembrar que o Next.js não te obriga a usar nenhum deles. Client-Side Rendering ainda existe e tem seu lugar.
// Isso aqui é só CSR puro — componente renderiza no cliente
'use client'
import { useEffect, useState } from 'react'
export function UserDashboard({ userId }: { userId: string }) {
const [data, setData] = useState(null)
useEffect(() => {
fetch(`/api/dashboard/${userId}`).then(r => r.json()).then(setData)
}, [userId])
if (!data) return <Skeleton />
return <Dashboard data={data} />
}
Quando faz sentido: dados privados por usuário (dashboard, configurações, histórico de pedidos). Não tem por que pré-renderizar no servidor algo que só aquele usuário específico pode ver.
Quando não faz sentido: conteúdo público com SEO relevante, páginas de produto, landing pages. Aí você está desperdiçando a infraestrutura que o Next.js entrega de graça.
SSG: quando o conteúdo não muda (ou quase não muda)
Static Site Generation gera o HTML no momento do build. O arquivo vai para uma CDN. Cada request serve o mesmo arquivo estático sem tocar em nenhum servidor de aplicação.
// app/blog/[slug]/page.tsx — App Router
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map(post => ({ slug: post.slug }))
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
return <Article post={post} />
}
// pages/blog/[slug].tsx — Pages Router
export async function getStaticPaths() {
const posts = await getPosts()
return {
paths: posts.map(post => ({ params: { slug: post.slug } })),
fallback: 'blocking'
}
}
export async function getStaticProps({ params }) {
const post = await getPostBySlug(params.slug)
return { props: { post } }
}
O fallback: 'blocking' merece atenção. Com false, qualquer rota não listada no getStaticPaths retorna 404. Com 'blocking', o Next.js faz SSR na primeira requisição e depois cacheia. Útil quando você tem milhares de páginas e não quer buildá-las todas de uma vez.
Casos de uso reais: posts de blog, páginas de documentação, landing pages de marketing, páginas de categorias de e-commerce com conteúdo estável.
Cuidado: SSG com dados de API externa significa que seu HTML é tão fresco quanto seu último deploy. Se o produto mudou de preço e você não fez deploy, o usuário vai ver o preço antigo.
SSR: dados frescos, custo real
Server-Side Rendering gera o HTML a cada request. Você paga com latência e custo de servidor, e recebe dados sempre atualizados.
// app/products/[id]/page.tsx — App Router (Server Component por padrão)
export const dynamic = 'force-dynamic' // garante SSR, sem cache
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
cache: 'no-store' // sem cache no fetch também
}).then(r => r.json())
return <ProductDetail product={product} />
}
// pages/products/[id].tsx — Pages Router
export async function getServerSideProps({ params, req }) {
const product = await getProduct(params.id)
// Acesso ao request: headers, cookies, IP
const userRegion = req.headers['cf-ipcountry']
return {
props: { product, userRegion }
}
}
Num projeto que participei, a equipe usou SSR em todas as páginas de produto de um e-commerce — eram 40 mil SKUs. O TTFB médio ficou em 600ms e o custo de função serverless foi absurdo. A maioria dessas páginas tinha dados que mudavam uma vez por dia no máximo. ISR teria resolvido com 1% do custo.
Quando SSR faz sentido de verdade:
- Páginas que dependem de dados do request (cookies de autenticação, geolocalização, A/B test por header)
- Conteúdo altamente personalizado que não pode ser cacheado
- Dados que mudam por segundo e têm impacto real se desatualizados (preço de ação, placar ao vivo)
⚠️ Atenção: No App Router, todo Server Component é SSR por padrão se você usar
fetchcomcache: 'no-store'ou acessarheaders()/cookies(). Isso pode te surpreender se você não configurou explicitamente o comportamento de cache.
ISR: o meio-termo que parece mágica
Incremental Static Regeneration gera o HTML estático, mas com uma validade. Depois do revalidate, o próximo request dispara uma regeneração em background — e o usuário ainda recebe a versão cacheada enquanto a nova é gerada.
// app/products/[id]/page.tsx — App Router
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: { revalidate: 60 } // revalida a cada 60 segundos
}).then(r => r.json())
return <ProductDetail product={product} />
}
// pages/products/[id].tsx — Pages Router
export async function getStaticProps({ params }) {
const product = await getProduct(params.id)
return {
props: { product },
revalidate: 60 // segundos
}
}
O modelo "stale-while-revalidate" significa que o usuário pode ver dados com até revalidate segundos de atraso. Para a maioria dos conteúdos, isso é perfeitamente aceitável.
Revalidação sob demanda (Next.js 13+) é ainda melhor quando você controla quando o dado muda:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache'
import { NextRequest } from 'next/server'
export async function POST(req: NextRequest) {
const { path, tag, secret } = await req.json()
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
if (tag) revalidateTag(tag)
if (path) revalidatePath(path)
return Response.json({ revalidated: true })
}
Aí você dispara esse endpoint via webhook do seu CMS quando um conteúdo é publicado. O melhor dos dois mundos: estático até mudar, atualizado quando precisa.
Comparação direta: qual usar em cada cenário
| Cenário | Estratégia | Motivo |
|---|---|---|
| Blog post, documentação | SSG | Conteúdo estático, SEO crítico, build atualiza |
| Página de produto (e-commerce) | ISR (60-300s) | Dados mudam, mas tolerância a delay existe |
| Dashboard do usuário | CSR | Dados privados, não indexável |
| Feed de notícias em tempo real | SSR | Atualização constante, sem tolerância a delay |
| Checkout / carrinho | CSR + API Routes | Dados de sessão, privado, transacional |
| Página de categoria com filtros | SSG + CSR | Estrutura estática, filtros no cliente |
| Preço com variação por usuário | SSR | Personalização por request |
| Landing page de campanha | SSG | Não muda até novo deploy |
Erros comuns que aparecem em code review
1. SSR para conteúdo que não precisa de frescor
❌ getServerSideProps pra uma FAQ que muda uma vez por mês
✓ getStaticProps com revalidate: 86400 (24h) — ou SSG puro
2. ISR com revalidate muito alto em dados críticos
❌ revalidate: 3600 pra preço de produto em promoção relâmpago
✓ revalidate: 30 + revalidação sob demanda via webhook
3. Misturar cache: 'no-store' e revalidate no mesmo fetch
❌
const data = await fetch(url, {
cache: 'no-store',
next: { revalidate: 60 } // ignorado quando cache: 'no-store'
})
✓
const data = await fetch(url, {
next: { revalidate: 60 }
})
4. Esquecer o fallback no getStaticPaths
Sem fallback: 'blocking' em catálogos grandes, você buildando 50 mil páginas no CI ou tendo 404 em rotas válidas. O CI vai quebrar por timeout antes de terminar.
5. Usar SSR por medo de ISR ser "arriscado"
ISR com stale-while-revalidate é seguro na imensa maioria dos casos. O medo de "e se o usuário ver dados velhos?" raramente justifica o custo de SSR em escala.
FAQ
SSR e SSG podem coexistir no mesmo projeto Next.js?
Sim, e é o padrão. Cada página define sua própria estratégia de renderização independentemente. Você pode ter /blog/[slug] como SSG com ISR e /checkout como CSR, tudo no mesmo projeto sem conflito.
O App Router muda alguma coisa nessa lógica?
A lógica de negócio não muda — você ainda está decidindo entre estático, dinâmico e cacheado. O que muda é a API: getStaticProps/getServerSideProps saem de cena. No App Router, o comportamento é controlado pelo cache do fetch, pela opção dynamic do segmento e pelas funções revalidatePath/revalidateTag. O conceito é o mesmo, a forma de declarar é diferente.
ISR funciona fora do Vercel?
Funciona, mas com ressalvas. O ISR do Next.js depende de um sistema de cache de filesystem ou de uma camada de cache configurável. Em deploy próprio com next start, o cache é local na instância — se você tem múltiplas instâncias sem storage compartilhado, cada uma tem seu próprio estado de cache. Para ambientes distribuídos fora do Vercel, vale configurar o cacheHandler customizado ou usar um proxy de cache na frente.
Como saber se minha página está sendo servida como estática ou dinâmica em produção?
O header x-nextjs-cache na resposta diz: HIT (servido do cache), MISS (gerado agora, vai cachear), STALE (ISR servindo versão antiga enquanto regenera), BYPASS (cache pulado). Inspecione no DevTools ou via curl -I.
Revalidação sob demanda é confiável para e-commerce?
É uma das abordagens mais sólidas para esse caso. O padrão comum é: produto atualizado no CMS/ERP → webhook dispara o endpoint de revalidação → revalidatePath('/products/[slug]') invalida o cache. Com isso, você tem latência de CDN (< 50ms) com dados atualizados em segundos após a mudança. O risco real está em o webhook falhar silenciosamente — sempre logue e monitore as chamadas de revalidação.
Próximos passos
Revise cada página do seu projeto Next.js com essa pergunta: "com que frequência esse dado muda e quem precisa ver a versão atual?"
Se a resposta for "raramente" → SSG. Se for "às vezes, com margem de minutos/horas" → ISR. Se for "sempre, por usuário ou por segundo" → SSR ou CSR.
Para ir além:
- Leia a documentação do Next.js sobre caching no App Router — é densa mas cobre casos de borda que você vai bater em produção
- Configure o header
Cache-Controlcustomizado vianext.config.jspara ter controle fino sobre o que o CDN cacheia - Em produção, monitore o
x-nextjs-cachedas suas rotas mais acessadas — surpresas acontecem quando o comportamento real difere do esperado