Next.js: quando usar SSR, SSG e ISR (e quando não usar nenhum)
← Voltar para Codeshort

Next.js: quando usar SSR, SSG e ISR (e quando não usar nenhum)

SSR, SSG e ISR não são intercambiáveis. Escolha errada e você paga com latência, custo de servidor ou dados velhos no pior momento.

DC
Dev Code Software
15 de junho de 2026·7 min de leitura

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 fetch com cache: 'no-store' ou acessar headers()/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árioEstratégiaMotivo
Blog post, documentaçãoSSGConteú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árioCSRDados privados, não indexável
Feed de notícias em tempo realSSRAtualização constante, sem tolerância a delay
Checkout / carrinhoCSR + API RoutesDados de sessão, privado, transacional
Página de categoria com filtrosSSG + CSREstrutura estática, filtros no cliente
Preço com variação por usuárioSSRPersonalização por request
Landing page de campanhaSSGNã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-Control customizado via next.config.js para ter controle fino sobre o que o CDN cacheia
  • Em produção, monitore o x-nextjs-cache das suas rotas mais acessadas — surpresas acontecem quando o comportamento real difere do esperado