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.