O que é Next.js?
Next.js é um framework React que transforma uma biblioteca de UI em uma plataforma de aplicação completa. Ele adiciona roteamento baseado em arquivos, renderização no servidor, busca de dados (data fetching), endpoints de API, otimização de imagens e fontes, e um runtime de servidor. Em vez de escolher e configurar manualmente um roteador, uma camada de dados e um setup de build, você recebe uma stack coerente projetada para trabalhar em conjunto.
Ele é mantido pela Vercel e se tornou a maneira padrão de construir aplicações React para produção. O modelo atual é o App Router, construído sobre React Server Components, que altera onde seu código é executado e como os dados fluem.
O App Router e o roteamento baseado em arquivos
As rotas são derivadas do sistema de arquivos. Uma pasta representa um segmento da URL, e um arquivo page.tsx a transforma em uma rota.
app/
├── layout.tsx # root layout, wraps everything
├── page.tsx # /
├── about/page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/page.tsx # /blog/:slug
└── dashboard/
├── layout.tsx # layout for /dashboard/*
└── page.tsx # /dashboard
Segmentos dinâmicos utilizam colchetes, e os layouts são aninhados para que a UI compartilhada permaneça montada durante a navegação. Arquivos especiais como loading.tsx, error.tsx e not-found.tsx gerenciam os estados correspondentes sem a necessidade de código extra.
// app/blog/[slug]/page.tsx
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
Componentes de servidor e de cliente
Este é o conceito que define o Next.js moderno. Por padrão, os componentes são server components: eles são renderizados no servidor, podem ler dados e segredos diretamente e não adicionam nada ao bundle do navegador. Componentes que precisam de interatividade optam por isso com "use client".
// app/counter.tsx
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Um server component pode importar e renderizar um client component, mas não o contrário. A regra de ouro é: mantenha os componentes no servidor e empurre a "use client" para a menor folha interativa possível. Isso mantém reduzido o JavaScript que você envia para o navegador.
Busca de dados
No App Router, você busca dados em server components utilizando a função padrão fetch, estendida com opções de cache e revalidação.
// app/posts/page.tsx
async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 60 },
});
if (!res.ok) throw new Error("Failed to load posts");
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return <PostList posts={posts} />;
}
Como o código é executado no servidor, não há necessidade de configurar spinners de carregamento nem lidar com waterfalls no cliente. Você pode revalidar por tempo, sob demanda com revalidatePath ou revalidateTag, ou desativar completamente o cache. As requisições fetch também são deduplicadas dentro de uma renderização.
Route handlers e server actions
Para endpoints de API, os route handlers exportam funções que recebem um web Request e retornam um Response.
// app/api/posts/route.ts
export async function GET() {
const posts = await db.post.findMany();
return Response.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await db.post.create({ data: body });
return Response.json(post, { status: 201 });
}
Para mutações vindas da sua própria UI, server actions costumam ser mais simples. Marque uma função com "use server" e chame-a a partir de um formulário ou de um manipulador de eventos.
// app/posts/new/page.tsx
import { revalidatePath } from "next/cache";
async function createPost(formData: FormData) {
"use server";
await db.post.create({ data: { title: String(formData.get("title")) } });
revalidatePath("/posts");
}
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Create</button>
</form>
);
}
As server actions lidam com validação, escritas no banco de dados e revalidação de cache de forma conjunta, e o formulário funciona antes mesmo do JavaScript carregar. Isso é aprimoramento progressivo sem trabalho extra.
Renderização e cache
O Next.js suporta diversas estratégias de renderização em um único app:
- Páginas Estáticas são pré-renderizadas no momento do build.
- Páginas Renderizadas no servidor são geradas a cada requisição.
- O Streaming envia o HTML em pedaços (chunks) para que dados lentos não bloqueiem a estrutura da página.
- A Renderização no cliente gerencia as ilhas interativas após a hidratação.
O cache possui diversas camadas, desde o cache do fetch até o cache completo da rota. Compreendê-las é a principal curva de aprendizado, e os padrões foram escolhidos para serem rápidos. Quando algo parecer desatualizado, a solução geralmente é uma chamada de revalidação explícita, em vez de desativar o cache em todo o projeto.
Otimização nativa
O Next.js já vem com componentes que lidam com as tarefas de performance mais comuns:
next/imageredimensiona, faz lazy-load e serve formatos modernos.next/fonthospeda fontes localmente e remove o layout shift.next/scriptcontrola a forma como scripts de terceiros são carregados.- Turbopack acelera o desenvolvimento e os builds de produção.
Esses padrões significam que um app Next.js bem construído é rápido sem a necessidade de um projeto de otimização separado.
Melhores práticas
- Mantenha os componentes no servidor e use
"use client"apenas onde for necessário. - Busque os dados em server components em vez de usar effects.
- Use server actions para mutações e faça a revalidação explicitamente.
- Coloque rotas, componentes e acesso a dados juntos sob
app/. - Adicione
loading.tsxeerror.tsxpara garantir uma boa UX nos limites das rotas. - Prefira os componentes de imagem e fonte do framework em vez do gerenciamento manual.
- Entenda o funcionamento do caching antes de desativá-lo; opte por revalidações direcionadas.
Erros comuns
- Espalhar
"use client"no topo da árvore e perder os benefícios do servidor. - Fazer fetch em
useEffectquando um server component resolveria. - Esquecer de revalidar após uma mutação e exibir dados obsoletos.
- Assumir que as APIs do App Router e do Pages Router são intercambiáveis.
- Bloquear uma rota com dados lentos em vez de utilizar streaming com Suspense.
- Tratar o Next.js apenas como uma ferramenta de front-end e reconstruir um backend separado.
Próximos passos
O Next.js é a maneira mais completa de entregar aplicações React hoje em dia. Reforce seus fundamentos de React e TypeScript, compare o modelo com Remix e Astro, e aprenda sobre o runtime Node.js onde ele é implantado. Depois, construa um pequeno app full-stack com um server component, um route handler e uma server action.