Guia Completo de Next.js Avançado: SSR, SSG, ISR e App Router com Server Components Já leu

Renderização no Next.js: Entendendo SSR, SSG e ISR A escolha da estratégia de renderização é fundamental para otimizar performance e UX em aplicações modernas. Next.js oferece três abordagens principais que funcionam em complementaridade. SSR (Server-Side Rendering) executa a renderização no servidor a cada requisição, garantindo conteúdo sempre fresco. Ideal para dados dinâmicos que mudam frequentemente. SSG (Static Site Generation) pré-renderiza páginas em tempo de build, servindo HTML estático — perfeito para conteúdo imutável com altíssima performance. ISR (Incremental Static Regeneration) combina o melhor dos dois mundos: pre-renderiza estaticamente mas regenera em background quando necessário, sem rebuild completo. https://api.example.com/posts/${params.id} App Router e Server Components: A Nova Arquitetura O App Router (diretor de aplicação) representa a evolução do Next.js, introduzindo uma estrutura baseada em diretórios mais intuitiva e Server Components como padrão. Server Components rodam apenas no servidor, reduzindo JavaScript enviado ao cliente e melhorando segurança — credenciais não são expostas. A estrutura usa diretórios para definir rotas: cria a rota .

Renderização no Next.js: Entendendo SSR, SSG e ISR

A escolha da estratégia de renderização é fundamental para otimizar performance e UX em aplicações modernas. Next.js oferece três abordagens principais que funcionam em complementaridade.

SSR (Server-Side Rendering) executa a renderização no servidor a cada requisição, garantindo conteúdo sempre fresco. Ideal para dados dinâmicos que mudam frequentemente. SSG (Static Site Generation) pré-renderiza páginas em tempo de build, servindo HTML estático — perfeito para conteúdo imutável com altíssima performance. ISR (Incremental Static Regeneration) combina o melhor dos dois mundos: pre-renderiza estaticamente mas regenera em background quando necessário, sem rebuild completo.

// SSG com revalidação (ISR)
export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.example.com/posts/${params.id}`);
  const post = await res.json();

  return {
    props: { post },
    revalidate: 3600, // Regenera a cada 1 hora
  };
}

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }],
    fallback: 'blocking', // Gera sob demanda se não existir
  };
}

export default function Post({ post }) {
  return <h1>{post.title}</h1>;
}

App Router e Server Components: A Nova Arquitetura

O App Router (diretor de aplicação) representa a evolução do Next.js, introduzindo uma estrutura baseada em diretórios mais intuitiva e Server Components como padrão. Server Components rodam apenas no servidor, reduzindo JavaScript enviado ao cliente e melhorando segurança — credenciais não são expostas.

A estrutura usa diretórios para definir rotas: app/dashboard/page.js cria a rota /dashboard. Layouts compartilhados são naturais — app/layout.js envolve todas as páginas, e app/dashboard/layout.js envolve apenas páginas internas. Isso elimina re-renders desnecessários em navegação.

// app/layout.js - Layout raiz (Server Component por padrão)
export default function RootLayout({ children }) {
  return (
    <html>
      <head><title>Meu App</title></head>
      <body>
        <nav>Menu Principal</nav>
        {children}
      </body>
    </html>
  );
}

// app/blog/[id]/page.js - Server Component com dados dinâmicos
async function fetchPost(id) {
  const res = await fetch(`https://api.example.com/posts/${id}`, {
    next: { revalidate: 60 }, // ISR automático
  });
  return res.json();
}

export default async function BlogPost({ params }) {
  const post = await fetchPost(params.id);
  return <article><h1>{post.title}</h1><p>{post.content}</p></article>;
}

Client Components e Interatividade

Quando você precisa de estado, event listeners ou hooks do React, use 'use client' no topo do arquivo. Esses componentes rodam no navegador. A estratégia ideal é manter a maioria do app como Server Components e usar Client Components apenas onde necessário — para formulários, filtros, modais.

// app/components/SearchFilter.js
'use client';

import { useState } from 'react';

export default function SearchFilter() {
  const [query, setQuery] = useState('');

  return (
    <input
      type="text"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Buscar..."
    />
  );
}

// app/products/page.js - Server Component que integra o Client Component
import SearchFilter from '@/components/SearchFilter';

async function fetchProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 300 },
  });
  return res.json();
}

export default async function ProductsPage() {
  const products = await fetchProducts();
  return (
    <div>
      <SearchFilter />
      <ul>
        {products.map(p => <li key={p.id}>{p.name}</li>)}
      </ul>
    </div>
  );
}

Middleware, Rotas de API e Otimizações Avançadas

Middleware permite interceptar requisições antes de alcançarem rotas ou páginas, perfeito para autenticação, redirecionamentos e log. Crie um arquivo middleware.js na raiz de src/ ou app/.

// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
  const token = request.cookies.get('auth_token');

  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
};

Rotas de API no App Router seguem a mesma filosofia: app/api/posts/route.js cria um endpoint /api/posts. Use exportações nomeadas para cada método HTTP.

// app/api/posts/route.js
export async function GET(request) {
  const posts = await fetchPostsFromDB();
  return Response.json(posts);
}

export async function POST(request) {
  const data = await request.json();
  const newPost = await savePostToDB(data);
  return Response.json(newPost, { status: 201 });
}

Para otimização avançada, use Image do Next.js para otimização automática de imagens, dynamic() para lazy loading de componentes pesados, e Suspense para progressive rendering com Server Components. O Image component serve múltiplos formatos, aplica lazy load nativo e responsive images automaticamente.

import Image from 'next/image';
import dynamic from 'next/dynamic';
import { Suspense } from 'react';

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>Carregando gráfico...</p>,
});

export default async function Dashboard() {
  return (
    <>
      <Image src="/hero.jpg" alt="Hero" width={1200} height={400} priority />

      <Suspense fallback={<p>Carregando dados...</p>}>
        <DataSection />
      </Suspense>

      <HeavyChart />
    </>
  );
}

async function DataSection() {
  const data = await fetch('https://api.example.com/data').then(r => r.json());
  return <div>{/* renderiza dados */}</div>;
}

Conclusão

Dominar Next.js avançado significa entender que cada página merece uma estratégia diferente: use SSG para blogs e documentação, ISR para catálogos que atualizam periodicamente, SSR para dashboards personalizados. Server Components são o novo padrão — construa com eles por padrão e use Client Components cirurgicamente apenas para interatividade real. Otimização é arquitetura: middleware para segurança, Image para performance, Suspense para UX progressiva — tudo integrado naturalmente no framework.

Referências


Artigos relacionados