Introdução ao Next.js: SSR e SSG

1. O que é Next.js e por que ele existe?

O ecossistema JavaScript evoluiu significativamente desde o surgimento do React em 2013. As Single Page Applications (SPAs) tradicionais, embora poderosas para interatividade no cliente, apresentavam limitações críticas: baixo desempenho em SEO (motores de busca não executavam JavaScript) e tempo de carregamento inicial lento (o navegador precisava baixar todo o bundle JavaScript antes de renderizar qualquer conteúdo).

Next.js, criado pela Vercel em 2016, surgiu como uma solução full-stack para React que resolve esses problemas ao oferecer renderização tanto no servidor quanto no cliente. Ele se baseia em três pilares fundamentais:

  • Páginas baseadas em arquivos: cada arquivo na pasta pages/ torna-se automaticamente uma rota
  • Renderização híbrida: SSR (Server-Side Rendering), SSG (Static Site Generation) e CSR (Client-Side Rendering) na mesma aplicação
  • Ecossistema Node.js: roda inteiramente sobre Node.js, permitindo acesso a APIs do servidor, bancos de dados e sistemas de arquivos

2. Renderização do lado do servidor (SSR) no Next.js

No SSR, quando um usuário faz uma requisição, o servidor Node.js executa o código React, gera o HTML completo e o envia ao navegador. Isso significa que o conteúdo já está pronto quando o JavaScript chega ao cliente.

Implementação prática com getServerSideProps:

// pages/produtos/[id].js
export default function Produto({ produto }) {
  return (
    <div>
      <h1>{produto.nome}</h1>
      <p>{produto.descricao}</p>
      <span>Preço: R$ {produto.preco}</span>
    </div>
  );
}

export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`https://api.exemplo.com/produtos/${id}`);
  const produto = await res.json();

  return {
    props: { produto },
  };
}

Vantagens:
- SEO completo: motores de busca veem o HTML final
- Dados sempre atualizados em cada requisição
- Ideal para páginas com conteúdo dinâmico e personalizado

Desvantagens:
- Maior tempo de resposta no servidor (TTFB mais alto)
- Custo computacional por requisição
- Necessidade de infraestrutura de servidor dedicada

3. Geração de sites estáticos (SSG) no Next.js

SSG pré-renderiza as páginas durante o tempo de build, gerando arquivos HTML estáticos que podem ser servidos via CDN. Isso oferece performance excepcional, pois não há processamento no servidor para cada requisição.

Implementação prática com getStaticProps:

// pages/blog.js
export default function Blog({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.titulo}</li>
      ))}
    </ul>
  );
}

export async function getStaticProps() {
  const res = await fetch('https://api.exemplo.com/posts');
  const posts = await res.json();

  return {
    props: { posts },
    revalidate: 3600, // Revalida a cada 1 hora (ISR)
  };
}

Diferenças chave entre SSG e SSR:

Característica SSG SSR
Momento da renderização Build Cada requisição
Performance Excelente (CDN) Variável (servidor)
Dados atualizados Requer rebuild/revalidação Sempre atualizados
Ideal para Blogs, documentação, landing pages Dashboards, e-commerce, dados personalizados

4. Rotas dinâmicas e getStaticPaths

Para páginas SSG com parâmetros dinâmicos, usamos getStaticPaths para definir quais caminhos serão pré-renderizados.

// pages/posts/[slug].js
export default function Post({ post }) {
  return (
    <article>
      <h1>{post.titulo}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.conteudo }} />
    </article>
  );
}

export async function getStaticPaths() {
  const res = await fetch('https://api.exemplo.com/posts');
  const posts = await res.json();

  const paths = posts.map(post => ({
    params: { slug: post.slug },
  }));

  return { paths, fallback: 'blocking' };
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.exemplo.com/posts/${params.slug}`);
  const post = await res.json();

  return {
    props: { post },
    revalidate: 60,
  };
}

O fallback: 'blocking' permite que páginas não pré-renderizadas sejam geradas sob demanda na primeira requisição, combinando SSG com geração incremental (ISR).

5. Data fetching híbrido: combinando SSR, SSG e CSR

Estratégias mistas permitem otimizar cada parte da página conforme sua necessidade:

// pages/perfil.js - SSG para dados estáticos + CSR para dados dinâmicos
import useSWR from 'swr';

export default function Perfil({ usuario }) {
  // Dados estáticos (SSG)
  const { data: comentarios, error } = useSWR(
    `/api/comentarios/${usuario.id}`,
    fetcher
  );

  if (error) return <div>Erro ao carregar comentários</div>;
  if (!comentarios) return <div>Carregando...</div>;

  return (
    <div>
      <h1>{usuario.nome}</h1>
      <p>Bio: {usuario.bio}</p>
      <h2>Comentários recentes:</h2>
      <ul>
        {comentarios.map(com => (
          <li key={com.id}>{com.texto}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  const res = await fetch('https://api.exemplo.com/usuario/1');
  const usuario = await res.json();

  return {
    props: { usuario },
    revalidate: 86400, // Revalida a cada 24h
  };
}

Neste exemplo, o perfil do usuário é estático (SSG), enquanto os comentários são carregados via CSR com SWR, garantindo dados sempre frescos sem sacrificar a performance inicial.

6. Performance e boas práticas com SSR e SSG

Otimizações essenciais:

// pages/dashboard.js - SSR com lazy loading
import dynamic from 'next/dynamic';

const GraficoPesado = dynamic(() => import('../components/Grafico'), {
  loading: () => <p>Carregando gráfico...</p>,
  ssr: false, // Não renderiza no servidor
});

export default function Dashboard({ dados }) {
  return (
    <div>
      <h1>Dashboard</h1>
      <GraficoPesado dados={dados} />
    </div>
  );
}

export async function getServerSideProps() {
  const res = await fetch('https://api.exemplo.com/dashboard');
  const dados = await res.json();

  return {
    props: { dados },
  };
}

Comparação de métricas:

Métrica SSR SSG SPA Puro
TTFB Médio (100-500ms) Baixo (<50ms) Baixo
FCP Rápido Rápido Lento
LCP Médio Rápido Lento
SEO Excelente Excelente Ruim

7. Deploy e considerações finais

Checklist para escolher a estratégia de renderização:

  • SSG: Conteúdo que raramente muda (blog, documentação, landing pages)
  • SSR: Dados personalizados por usuário (dashboards, e-commerce com preços dinâmicos)
  • CSR: Funcionalidades pós-carregamento (comentários, chat, formulários complexos)
  • ISR: Conteúdo que muda periodicamente (notícias, catálogos de produtos)

Comandos para deploy:

# Build para produção
next build

# Iniciar servidor Node.js (para SSR)
next start

# Gerar exportação estática (apenas SSG)
next export

Opções de deploy:
- Vercel: Suporte nativo, deploy automático via Git
- Netlify: Suporte a SSG com adaptações
- Servidor Node.js próprio: Controle total sobre SSR

Next.js oferece flexibilidade incomparável ao permitir escolher a estratégia ideal para cada página, combinando o melhor dos mundos estático e dinâmico.

Referências