Este documento visa esclarecer os conceitos de Server-Side Rendering (SSR), Client-Side Rendering (CSR) e API Routes (agora chamadas Route Handlers) na versão mais recente do Next.js, que utiliza o App Router. Abordaremos também como realizar chamadas a APIs externas utilizando os Route Handlers como intermediários.
O Next.js, com a introdução do App Router, trouxe uma nova abordagem para a renderização, baseada primariamente em React Server Components (RSCs). Por padrão, todos os componentes dentro do diretório app são Server Components. Isso significa que eles são renderizados exclusivamente no servidor.
Os Server Components permitem que a lógica de busca de dados e renderização ocorra no servidor, enviando para o cliente um HTML mais completo e reduzindo a quantidade de JavaScript necessária no navegador. Isso geralmente resulta em melhor performance inicial e SEO.
Dentro da renderização no servidor, temos duas abordagens principais no App Router:
Renderização Estática (Static Rendering): Este é o comportamento padrão para Server Components. O trabalho de renderização e busca de dados é feito no momento da build (quando você faz o deploy da aplicação) ou durante a revalidação de dados (Incremental Static Regeneration - ISR). O resultado é cacheado e distribuído globalmente (por exemplo, via CDN), proporcionando carregamentos extremamente rápidos e menor carga no servidor. É ideal para páginas cujo conteúdo não muda frequentemente ou é o mesmo para todos os usuários, como posts de blog, páginas de produto ou documentação.
Renderização Dinâmica (Dynamic Rendering): A renderização dinâmica ocorre no servidor, mas a cada request do usuário (no momento em que ele visita a página). Isso é necessário quando a página precisa exibir dados em tempo real, conteúdo personalizado para o usuário ou acessar informações que só estão disponíveis no momento da requisição (como cookies ou parâmetros de busca da URL). O Next.js automaticamente opta pela renderização dinâmica se detectar o uso de funções dinâmicas (como cookies() ou headers() de next/headers, ou o uso de searchParams) ou se a busca de dados for configurada como dinâmica (por exemplo, fetch(..., { cache: 'no-store' })). Embora flexível, a renderização dinâmica pode ser mais lenta, pois o servidor precisa gerar o conteúdo a cada visita, e a performance da página fica atrelada à fonte de dados mais lenta.
Para adicionar interatividade no lado do cliente (eventos de clique, gerenciamento de estado com useState, useEffect, uso de APIs do navegador), você precisa usar Client Components. Para transformar um componente em Client Component, basta adicionar a diretiva `
'use client'` no topo do arquivo do componente. Client Components são pré-renderizados no servidor (para o HTML inicial) e depois "hidratados" no navegador, onde o JavaScript assume para adicionar interatividade. O processo de renderização que ocorre principalmente no navegador, após o carregamento inicial e a hidratação, é o que chamamos de Client-Side Rendering (CSR).
É importante notar que Client Components ainda são renderizados inicialmente no servidor pelo Next.js. A diretiva 'use client' marca o "limite" entre o servidor e o cliente. O código do Client Component é enviado para o cliente, e a renderização e interatividade ocorrem lá.
No App Router, as antigas API Routes do diretório pages/api foram substituídas pelos Route Handlers. Eles permitem criar endpoints de API customizados dentro do diretório app, utilizando as APIs Web padrão Request e Response.
Convenção: Route Handlers são definidos em um arquivo chamado route.js ou route.ts dentro de uma pasta no diretório app. O nome da pasta define o segmento da URL da API. Por exemplo, app/api/users/route.ts criaria um endpoint em /api/users.
Funcionalidade: Dentro de um arquivo route.ts, você exporta funções assíncronas nomeadas de acordo com os métodos HTTP que deseja suportar (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Cada função recebe um objeto Request (ou NextRequest, que estende o Request padrão com helpers) e deve retornar um objeto Response (ou NextResponse).
// Exemplo: app/api/exemplo/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
// Lógica para buscar dados
const dados = { mensagem: 'Olá do GET!' };
return NextResponse.json(dados);
}
export async function POST(request: Request) {
const body = await request.json();
// Lógica para processar o corpo da requisição
console.log(body);
return NextResponse.json({ recebido: body }, { status: 201 });
}
Caching: Por padrão, Route Handlers que usam o método GET não são cacheados. Para habilitar o cache (comportamento estático), você pode exportar a constante dynamic = 'force-static' no arquivo do handler ou configurar o cache na chamada fetch interna. Outros métodos HTTP (POST, PUT, etc.) nunca são cacheados.
Uma prática comum é usar Route Handlers como um proxy ou intermediário para chamar APIs externas. Isso oferece várias vantagens:
Fluxo:
fetch) para o seu endpoint interno do Next.js (o Route Handler). Ex: fetch('/api/dados-externos').fetch (ou outra biblioteca HTTP) para fazer a chamada para a API externa real. É aqui que você incluiria chaves de API (geralmente via variáveis de ambiente process.env.API_KEY), headers necessários, etc.NextResponse com os dados (ou um erro) para o seu frontend.Exemplo de Route Handler chamando API externa:
// app/api/dados-externos/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const apiKey = process.env.API_EXTERNA_KEY;
const urlApiExterna = 'https://api.exemplo.com/data';
if (!apiKey) {
return NextResponse.json({ error: 'Chave de API não configurada' }, { status: 500 });
}
try {
const responseExterna = await fetch(urlApiExterna, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
// Configurações de cache, se necessário (padrão do Next.js para fetch é cachear)
// next: { revalidate: 60 } // Revalidar a cada 60 segundos
cache: 'no-store' // Não cachear a resposta da API externa
});
if (!responseExterna.ok) {
// Lidar com erros da API externa
const errorData = await responseExterna.text();
return NextResponse.json({ error: `Erro da API externa: ${responseExterna.status} ${errorData}` }, { status: responseExterna.status });
}
const dados = await responseExterna.json();
// Retorna os dados para o frontend
return NextResponse.json(dados);
} catch (error) {
console.error('Erro ao chamar API externa:', error);
return NextResponse.json({ error: 'Erro interno do servidor ao buscar dados externos' }, { status: 500 });
}
}
Chamada do Frontend (Exemplo em um Client Component):
'use client';
import { useState, useEffect } from 'react';
export default function DadosExternosComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
setError(null);
const response = await fetch('/api/dados-externos'); // Chama o Route Handler
if (!response.ok) {
throw new Error(`Erro ao buscar dados: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <p>Carregando...</p>;
if (error) return <p>Erro: {error}</p>;
return (
<div>
<h2>Dados da API Externa:</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
O Next.js App Router oferece flexibilidade com Server Components (para renderização estática ou dinâmica no servidor) e Client Components (para interatividade e CSR). Os Route Handlers substituem as API Routes, fornecendo uma maneira robusta e baseada em padrões web para criar endpoints de API dentro da sua aplicação, sendo ideais para atuar como intermediários seguros e controlados para chamadas a APIs externas.