Cenários comuns onde desenvolvedores React e Next.js precisam escolher entre padrões, hooks ou abordagens arquitetônicas. Cada decisão apresenta a melhor, segunda melhor e terceira melhor escolha -- além da escolha errada que os desenvolvedores comumente fazem.
Crie um route handler GET e busque de um Server Component. Funciona, mas adiciona um salto de rede desnecessário.
3º
getServerSideProps (Pages Router)
Ainda funciona se estiver no Pages Router, mas você perde streaming e benefícios do RSC.
Escolha errada: Usar useEffect + fetch em um Client Component. Isso adiciona um waterfall cliente-servidor, expõe sua API e prejudica o SEO, pois o conteúdo não está no HTML inicial.
Por que a melhor é a melhor: Async Server Components buscam dados com zero JS no cliente, transmitem HTML progressivamente e acessam o banco de dados diretamente sem uma camada de API.
Cenário: Um usuário envia um formulário de contato que precisa de validação no servidor e exibição de erros.
Rank
Escolha
Abordagem
Melhor
Server Action + useActionState
Vincule uma Server Action a <form action={...}> e use useActionState para o estado pendente/erro.
2º
Server Action + estado manual
Chame a action via startTransition e gerencie seu próprio estado com useState. Mais boilerplate, mas controle total.
3º
API route + client fetch
POST para um route handler de um manipulador onSubmit. Funciona, mas contorna o aprimoramento progressivo.
Escolha errada: Validação apenas no cliente sem validação no servidor. Nunca confie no cliente -- atacantes contornam sua UI.
Por que a melhor é a melhor:useActionState fornece estados pendente, erro e dados automaticamente, funciona sem JS (aprimoramento progressivo) e mantém a lógica de validação no servidor.
Cenário: Seu painel tem uma barra lateral e um cabeçalho que devem persistir em /dashboard/analytics, /dashboard/settings, etc.
Rank
Escolha
Abordagem
Melhor
layout.tsx aninhado
Coloque um layout.tsx em app/dashboard/ com UI compartilhada. Rotas filhas renderizam dentro de {children}.
2º
Arquivo template
Use template.tsx em vez disso. Mesmo aninhamento, mas remonta na navegação (útil para animações).
3º
Componente wrapper
Importe um wrapper <DashboardShell> em cada página. Funciona, mas duplica a importação e quebra a preservação automática do layout.
Escolha errada: Colocar a lógica de layout em _app.tsx (Pages Router) ou um provedor de contexto que re-renderiza toda a árvore. Isso anula o propósito dos layouts aninhados.
Por que a melhor é a melhor:layout.tsx é preservado entre as navegações -- a barra lateral não re-renderiza quando você muda de aba, preservando o estado de rolagem e evitando trabalho desnecessário.
Cenário: Sua página de painel busca dados analíticos pesados e você precisa de um esqueleto de carregamento.
Rank
Escolha
Abordagem
Melhor
loading.tsx
Adicione um arquivo loading.tsx ao lado de page.tsx. Next.js envolve automaticamente a página em um limite <Suspense> com sua UI de carregamento.
2º
<Suspense> manual
Envolva componentes assíncronos específicos em <Suspense fallback={<Skeleton />}> para controle granular.
3º
Estado de carregamento no lado do cliente
Use useState(true) e defina false após a conclusão da busca useEffect.
Escolha errada: Não mostrar nada (tela em branco) enquanto os dados carregam. Os usuários pensam que o aplicativo está quebrado após ~300ms sem feedback.
Por que a melhor é a melhor:loading.tsx é automático, requer zero JS no cliente e permite navegação instantânea através da arquitetura de streaming do React.
Cenário: Um componente gerencia um formulário de várias etapas com 8 campos, estado de validação e navegação entre etapas.
Rank
Escolha
Abordagem
Melhor
useReducer
Defina ações como SET_FIELD, NEXT_STEP, VALIDATE. As transições de estado são explícitas e testáveis.
2º
useState com um objeto
const [form, setForm] = useState({...}). Mais simples, mas atualizações baseadas em spread ficam confusas com lógica complexa.
3º
Múltiplas chamadas useState
Uma para cada campo. Bom para formulários simples, mas 8+ chamadas useState são difíceis de coordenar.
Escolha errada: Procurar uma biblioteca de gerenciamento de estado global (Redux, Zustand) para o estado do formulário que vive apenas em um componente. Exagero massivo.
Por que a melhor é a melhor:useReducer centraliza transições de estado complexas em uma função pura que você pode testar unitariamente independentemente do React.
Cenário: Tema, localidade e preferências do usuário precisam ser acessíveis a 5+ níveis de profundidade.
Rank
Escolha
Abordagem
Melhor
React Context
Crie um provedor de contexto perto do topo. Consumidores leem via useContext ou use(ThemeContext) do React 19.
2º
Composição de componentes
Passe os dados como children ou render props para evitar que componentes intermediários precisem da prop.
3º
Prop drilling
Passe props por todos os níveis. Tedioso, mas explícito e fácil de rastrear.
Escolha errada: Instalar uma biblioteca de gerenciamento de estado apenas para evitar prop drilling. Contexto é embutido e suficiente para dados de leitura frequente e raramente alterados, como temas.
Por que a melhor é a melhor: Contexto é de dependência zero, embutido no React e otimizado para dados que mudam com pouca frequência, mas são lidos amplamente.
Defina o estado imediatamente, reverta em caso de erro em um try/catch. Mais código, mesma ideia.
3º
onMutate do React Query
Atualização otimista via callbacks de mutação. Bom, mas puxa uma biblioteca para algo que o React 19 faz nativamente.
Escolha errada: Esperar a resposta do servidor antes de atualizar a UI. O atraso de 200-500ms faz o aplicativo parecer lento.
Por que a melhor é a melhor:useOptimistic reverte automaticamente para o estado real quando a Server Action é concluída ou falha -- nenhuma lógica manual de rollback é necessária.
Cenário: A página /dashboard/billing pode falhar (erros de API de pagamento) e precisa de um fallback gracioso.
Rank
Escolha
Abordagem
Melhor
error.tsx
Adicione error.tsx ao lado da página. Captura erros de renderização e de dados, oferece um botão de retentar via reset().
2º
Componente Error Boundary
Envolva em um <ErrorBoundary> personalizado para mais controle sobre tipos de erro e relatórios.
3º
Try/catch em Server Component
Capture erros na função assíncrona e renderize a UI de fallback inline. Sem retentativa automática.
Escolha errada: Deixar os erros borrarem para uma página de erro global que substitui todo o layout. O usuário perde todo o contexto e estado de navegação.
Por que a melhor é a melhor:error.tsx é limitado ao segmento da rota -- o restante do layout permanece interativo, e reset() tenta renderizar novamente sem um recarregamento completo da página.
Cenário: Um componente exibe um cartão de produto com nome, preço, imagem e um botão "Adicionar ao Carrinho".
Rank
Escolha
Abordagem
Melhor
Dividir: cartão do servidor + botão do cliente
Mantenha ProductCard como um Server Component. Extraia <AddToCartButton> como um Client Component.
2º
Cartão inteiro como Client Component
Adicione "use client" ao cartão. Simples, mas envia mais JS do que o necessário.
3º
Server Component com wrapper de hidratação no lado do cliente
Envolva a parte interativa com um limite de hidratação genérico. Superengenharia para um botão.
Escolha errada: Adicionar "use client" a todos os componentes "por segurança". Isso anula os benefícios do RSC e envia JavaScript desnecessário.
Por que a melhor é a melhor: Empurre o limite "use client" o mais baixo possível. Apenas o botão interativo precisa de JS no cliente -- o cartão, a imagem e o texto renderizam com zero JS.
Cenário: Posts de blog são atualizados com pouca frequência. Você quer carregamentos rápidos, mas conteúdo fresco em minutos.
Rank
Escolha
Abordagem
Melhor
ISR com revalidate
export const revalidate = 60; em sua página ou layout. Serve HTML em cache, revalida em segundo plano.
2º
Revalidação sob demanda
Chame revalidatePath('/blog/[slug]') ou revalidateTag('posts') de um webhook do CMS. Frescor instantâneo.
3º
Totalmente estático (generateStaticParams)
Pré-renderiza todos os posts na compilação. Rápido, mas desatualizado até o próximo deploy.
Escolha errada: Tornar a página do blog totalmente dinâmica (export const dynamic = 'force-dynamic'). Sem cache significa que cada solicitação atinge o banco de dados -- lento e desperdício para conteúdo que mal muda.
Por que a melhor é a melhor: ISR baseado em tempo é zero-config, serve conteúdo desatualizado enquanto revalida e equilibra frescor com desempenho automaticamente.
Cenário:/dashboard deve ser acessível apenas a usuários logados.
Rank
Escolha
Abordagem
Melhor
Middleware
Verifique a sessão em middleware.ts e redirecione antes mesmo que a página seja renderizada. Zero flash de layout.
2º
Verificação em Server Component
Verifique a autenticação no Server Component da página e chame redirect('/login'). Funciona, mas o layout pode piscar.
3º
Guarda no lado do cliente
Verifique a autenticação em useEffect e redirecione. Exibe conteúdo protegido brevemente antes de redirecionar.
Escolha errada: Apenas ocultar o link de navegação para /dashboard. Segurança por obscuridade -- qualquer pessoa com o URL pode acessar a página.
Por que a melhor é a melhor: O middleware é executado na borda antes de qualquer renderização. O usuário nunca vê um flash de conteúdo protegido, e a verificação é centralizada para todas as rotas protegidas.
Embutido, sem dependência. Bom o suficiente para aplicativos pequenos, mas re-renderiza todos os consumidores em qualquer alteração.
3º
Redux Toolkit
Poderoso, mas pesado para um carrinho. Vale a pena apenas se o aplicativo já usa Redux para outro estado.
Escolha errada: Armazenar o estado do carrinho em um Server Component ou abordagem apenas de cookie sem reatividade do cliente. A UI não será atualizada quando itens forem adicionados sem um recarregamento completo da página.
Por que a melhor é a melhor: Zustand tem ~1KB, não requer provedores, suporta seletores para evitar re-renderizações desnecessárias e funciona naturalmente com os recursos concorrentes do React 18/19.
Envolva cada seção em seu próprio <Suspense>. Eles transmitem independentemente à medida que resolvem.
3º
awaits sequenciais
const user = await getUser(); const analytics = await getAnalytics(); -- simples, mas cria um waterfall.
Escolha errada: Buscar todos os três em um único useEffect sequencialmente. Três waterfalls mais renderização no lado do cliente tornam o painel dolorosamente lento.
Por que a melhor é a melhor:Promise.all dispara todas as três requisições simultaneamente. O tempo total de espera = a requisição mais lenta, não a soma de todas as três.
Cenário: Iniciando um novo aplicativo Next.js e escolhendo uma solução de estilo.
Rank
Escolha
Abordagem
Melhor
Tailwind CSS
Utilitário-first, sem runtime, funciona perfeitamente com Server Components, enorme ecossistema (shadcn/ui).
2º
CSS Modules
Estilos escopados, sem runtime, embutido no Next.js. Bom para equipes que preferem CSS tradicional.
3º
Vanilla Extract
CSS-in-TS type-safe sem runtime. Mais configuração, mas ótimo para equipes de design system.
Escolha errada: Usar uma biblioteca CSS-in-JS com runtime (styled-components, Emotion) com Server Components. Eles exigem renderização no lado do cliente e quebram o streaming do RSC.
Por que a melhor é a melhor: Tailwind tem custo zero de runtime, funciona com RSC e, combinado com shadcn/ui, fornece componentes acessíveis prontos para produção imediatamente.
<picture> manual com srcSet e uma CDN como Cloudinary. Controle total, mas mais trabalho.
3º
<img> puro com carregamento preguiçoso
<img loading="lazy" /> -- sem otimização, dimensionamento manual, potencial CLS.
Escolha errada: Usar tags <img> não otimizadas com imagens de origem em resolução total. Páginas carregam megabytes de imagens, matando os Core Web Vitals.
Por que a melhor é a melhor:next/image serve automaticamente WebP/AVIF, redimensiona para a largura do dispositivo, carrega preguiçosamente abaixo da dobra e evita CLS com dimensões obrigatórias.
Cenário: Seu aplicativo precisa consultar um banco de dados PostgreSQL a partir de código do lado do servidor.
Rank
Escolha
Abordagem
Melhor
Prisma em Server Components/Actions
Consulte diretamente: const users = await prisma.user.findMany(). Type-safe, sem necessidade de camada de API.
2º
Drizzle ORM
Mais leve, sintaxe semelhante a SQL, excelente inferência TypeScript. Ótimo para equipes que preferem SQL.
3º
SQL Bruto via pg ou postgres
Controle máximo, sem sobrecarga de ORM. Bom para consultas complexas, mas sem segurança de tipo sem ferramentas adicionais.
Escolha errada: Expor consultas de banco de dados através de API routes e buscá-las de Client Components. Adiciona latência, complexidade e superfície de ataque sem benefício quando RSC pode consultar diretamente.
Por que a melhor é a melhor: Prisma em Server Components significa consultas type-safe sem exposição ao cliente, pooling de conexão automático e migrações integradas.
Cenário: Uma página de listagem de produtos tem filtros (categoria, faixa de preço, ordenação) que os usuários querem compartilhar e marcar.
Rank
Escolha
Abordagem
Melhor
Parâmetros de busca (useSearchParams + Server Component)
Leia os parâmetros em um Server Component: searchParams.category. Filtre no servidor, retorne apenas os produtos correspondentes.
2º
Biblioteca nuqs
Gerenciamento de estado de URL type-safe com useQueryState. Lida com serialização e padrões elegantemente.
3º
useState no lado do cliente
Atualizações rápidas da UI, mas os filtros são perdidos ao recarregar, não podem ser compartilhados e prejudicam o SEO.
Escolha errada: Armazenar filtros apenas em useState. Os usuários não podem compartilhar visualizações filtradas, o botão voltar não funciona e os motores de busca não podem indexar páginas filtradas.
Por que a melhor é a melhor: Parâmetros de busca na URL tornam os filtros compartilháveis, marcáveis, amigáveis ao SSR e o servidor pode otimizar a consulta ao banco de dados com base nos filtros.
Cenário: Clicar em "Editar Perfil" deve abrir um modal sobreposto.
Rank
Escolha
Abordagem
Melhor
Rota interceptora + rota paralela
Duas funcionalidades do Next.js se combinam: uma rota paralela (@modal) renderiza um slot ao lado da página, e uma rota interceptora ((.)edit-profile) captura a navegação no lado do cliente e a renderiza dentro desse slot como um modal em vez de uma página completa. Veja a estrutura de arquivos e a explicação abaixo.
2º
Client Component com <dialog>
Use o elemento nativo <dialog> com useRef. Acessível, sem necessidade de biblioteca.
3º
Headless UI / Radix Dialog
Gerenciamento de foco, animações e acessibilidade gerenciados pela biblioteca. Confiável, mas adiciona uma dependência.
Como o padrão de rota interceptora + rota paralela funciona:
app/
layout.tsx ← renderiza {children} E {modal}
@modal/
default.tsx ← retorna null (sem modal por padrão)
(.)edit-profile/
page.tsx ← versão modal de edit-profile
edit-profile/
page.tsx ← versão de página completa de edit-profile
Rota paralela @modal - A pasta @modal define um "slot" nomeado. Em layout.tsx você o renderiza como uma prop: export default function Layout({ children, modal }) { return <>{children}{modal}</>; }. Por padrão, ele renderiza default.tsx (que retorna null - sem modal visível).
Rota interceptora (.)edit-profile - O prefixo (.) significa "interceptar esta rota no mesmo nível". Quando o usuário clica em um <Link href="/edit-profile"> (navegação suave/lado do cliente), o Next.js corresponde à rota interceptora dentro de @modal em vez da página real /edit-profile. Você a renderiza como um modal sobreposto.
Navegação forçada (URL direta, recarregamento) - Se alguém colar seusite.com/edit-profile no navegador ou recarregar, o interceptador não ativará. O Next.js renderiza o app/edit-profile/page.tsx completo como uma página regular. O slot do modal permanece null.
O resultado: Clicar em "Editar Perfil" abre um modal (rápido, sem mudança de página). Compartilhar o URL dá ao destinatário a página completa. Recarregar o URL do modal também mostra a página completa. Um URL, duas apresentações.
Escolha errada: Um div com display: none/block alternado via estado. Sem captura de foco, sem tratamento da tecla escape, não acessível para leitores de tela, e o modal não tem URL, então não pode ser compartilhado ou marcado.
Por que a melhor é a melhor: Rotas interceptoras dão ao modal um URL real -- os usuários podem compartilhá-lo, recarregar mostra uma página completa, e o modal do lado do cliente evita uma navegação completa. É o único padrão que fornece duas apresentações (modal vs. página) a partir de um único URL sem gerenciamento de estado extra.
Cenário: Seu aplicativo precisa de um recurso de busca para um catálogo de produtos com mais de 50.000 itens.
Rank
Escolha
Abordagem
Melhor
Busca no lado do servidor via parâmetros de busca
Debounce a entrada, envie para a URL: router.push(?q=term). Server Component consulta o banco de dados com ILIKE ou busca de texto completo.
2º
Serviço de busca dedicado
Use Algolia, Meilisearch ou Elasticsearch via uma Server Action. Melhor relevância e tolerância a erros de digitação.
3º
Filtro no lado do cliente com useMemo
Carregue todos os produtos e filtre no lado do cliente. Viável apenas para conjuntos de dados pequenos (menos de 500 itens).
Escolha errada: Carregar 50.000 produtos na memória do cliente e filtrar com .filter(). Trava navegadores móveis e desperdiça largura de banda.
Por que a melhor é a melhor: A busca no lado do servidor utiliza índices de banco de dados, envia apenas os resultados correspondentes pela rede e mantém a consulta de busca na URL para compartilhamento.
Cenário: Um componente renderiza uma lista filtrada derivada de props e passa um manipulador para componentes filhos.
Rank
Escolha
Abordagem
Melhor
Usar React Compiler (React 19)
Habilite o React Compiler -- ele memoiza automaticamente. Nenhuma necessidade de useMemo/useCallback manual.
2º
useMemo/useCallback direcionado
Memoize apenas o cálculo caro e o callback passado para filhos envoltos em memo().
3º
Memoizar tudo
Envolva todos os valores derivados e manipuladores. Adiciona sobrecarga de memória e complexidade para ganhos marginais.
Escolha errada: Nunca memoizar, mesmo quando você mediu um problema de desempenho. Se a profilagem mostrar uma re-renderização de 200ms de um filtro caro, useMemo é a correção certa.
Por que a melhor é a melhor: O React Compiler analisa estaticamente seu código e insere a memoização exatamente onde é necessário -- sem esforço do desenvolvedor, sem oportunidades perdidas, sem excesso de memoização.
Cenário: Seu aplicativo precisa de URLs de API diferentes para desenvolvimento, staging e produção.
Rank
Escolha
Abordagem
Melhor
.env.local + prefixo NEXT_PUBLIC_
Variáveis apenas para o servidor em .env.local. Variáveis expostas ao cliente prefixadas com NEXT_PUBLIC_. Next.js cuida do resto.
2º
Variáveis de ambiente da plataforma
Defina variáveis no painel Vercel/AWS. Mesma convenção, gerenciado externamente.
3º
Arquivo de configuração com switch de ambiente
config.ts com verificações process.env.NODE_ENV. Funciona, mas duplica o que os arquivos .env já fazem.
Escolha errada: Codificar URLs de API ou confirmar arquivos .env com segredos no git. Segredos vazam, a troca de ambiente falha.
Por que a melhor é a melhor: A convenção .env do Next.js é embutida, suporta substituições por ambiente (.env.production) e o prefixo NEXT_PUBLIC_ torna a fronteira servidor/cliente explícita.
Cenário: Uma tabela de admin exibe 10.000 registros de usuários e precisa de paginação.
Rank
Escolha
Abordagem
Melhor
Paginação no lado do servidor via parâmetros de busca
Página e limite vivem na URL (?page=2&limit=20). O Server Component os lê, consulta o banco de dados com OFFSET/LIMIT (ou equivalente) e retorna apenas essa página de linhas. Veja a explicação e o código abaixo.
2º
Paginação baseada em cursor
Use ?cursor=abc123 para paginação estável em dados que mudam frequentemente. Melhor para feeds em tempo real.
3º
Paginação no lado do cliente
Busque todos os dados, págine na memória. Só funciona para conjuntos de dados pequenos.
Como funciona a paginação no lado do servidor via parâmetros de busca:
URL é a fonte da verdade - A página atual e o tamanho da página são parâmetros de busca (?page=2&limit=20). Isso significa que o estado da paginação é compartilhável, marcável e sobrevive ao recarregamento. Os botões para frente/trás navegam pelas páginas gratuitamente.
Server Component lê os parâmetros e consulta - O page.tsx recebe searchParams como uma prop. Ele calcula OFFSET e LIMIT, consulta apenas essa fatia do banco de dados e também busca a contagem total para os controles de página.
Client Component lida com a navegação - Uma pequena barra de paginação "use client" usa useRouter ou <Link> para atualizar os parâmetros de busca. Nenhuma lógica de busca de dados no cliente.
Streaming funciona automaticamente - Como a busca de dados está em um Server Component, envolvê-la em <Suspense> exibe um esqueleto de carregamento enquanto a consulta é executada. A navegação entre páginas transmite o novo conteúdo.
Pontos chave: O banco de dados retorna apenas 20 linhas por solicitação (não 10.000). A URL informa exatamente em qual página você está. Mudar de página é uma navegação do servidor -- nenhum código de busca de dados do lado do cliente. <Link> com parâmetros de busca oferece pré-busca e streaming gratuitamente.
Escolha errada: Scroll infinito que busca todos os 10.000 registros na memória. Travamentos de navegador, pesadelo de acessibilidade e usuários não podem pular para a página 50.
Por que a melhor é a melhor: A paginação no lado do servidor coloca o estado da página na URL (compartilhável, marcável), busca apenas a fatia necessária do banco de dados, transmite HTML via RSC e requer zero lógica de busca de dados do lado do cliente.
Cenário: Um formulário precisa criar um novo registro no banco de dados.
Rank
Escolha
Abordagem
Melhor
Server Action
Função "use server" chamada de <form action={...}>. Type-safe, aprimoramento progressivo, sem endpoint manual.
2º
API Route (Route Handler)
app/api/records/route.ts com um handler POST. Melhor quando clientes externos (aplicativos móveis, webhooks) precisam do endpoint.
3º
tRPC
Segurança de tipo de ponta a ponta com um contrato cliente/servidor. Poderoso, mas pesado se Server Actions cobrirem suas necessidades.
Escolha errada: Criar API routes para mutações usadas apenas pelo seu próprio frontend Next.js. Server Actions eliminam o boilerplate de gerenciamento de endpoints, serialização e tratamento de erros.
Por que a melhor é a melhor: Server Actions estão co-localizadas com sua UI, lidam automaticamente com serialização, suportam aprimoramento progressivo e se integram com useActionState para estados pendentes/erro.
Cenário: Seu aplicativo tem mais de 30 rotas em várias áreas de recursos (autenticação, painel, configurações, marketing público).
Rank
Escolha
Abordagem
Melhor
Grupos de rotas + colocation de recursos
app/(auth)/login, app/(dashboard)/analytics, app/(marketing)/pricing. Layouts compartilhados por grupo.
2º
Pastas de recursos fora de app/
src/features/auth/, src/features/dashboard/ com componentes, hooks e utilitários. app/ contém apenas arquivos de rota finos.
3º
Estrutura plana de app/
Todas as rotas no nível superior. Funciona para aplicativos pequenos, mas se torna incontrolável com mais de 30 rotas.
Escolha errada: Organizar por tipo de arquivo (components/, hooks/, utils/) em vez de recurso. Desenvolvedores pulam entre 5 diretórios para trabalhar em um recurso.
Por que a melhor é a melhor: Grupos de rotas permitem compartilhar layouts e middleware por área de recurso, mantêm arquivos relacionados juntos e os nomes entre parênteses não afetam o URL.
Cenário: Seu aplicativo precisa exibir notificações ao vivo assim que chegam.
Rank
Escolha
Abordagem
Melhor
WebSockets (Socket.io ou nativo)
Conexão persistente, bidirecional, baixa latência. Use um Client Component para gerenciar a conexão.
2º
Server-Sent Events (SSE)
Push do servidor para o cliente em uma via. Mais simples que WebSockets, reconecta automaticamente, funciona através de proxies.
3º
Polling com setInterval
Busque a cada N segundos. Simples, mas desperdiça largura de banda e tem latência inerente.
Escolha errada: Polling a cada segundo com useEffect + fetch. Sobrecarrega o servidor, drena baterias de celular e ainda tem até 1 segundo de latência.
Por que a melhor é a melhor: WebSockets entregam mensagens instantaneamente com uma única conexão persistente, suportando envio e recebimento sem sobrecarga repetida de HTTP.
Cenário: Um <CheckoutForm> componente lida com validação, envio e exibição de erros.
Rank
Escolha
Abordagem
Melhor
React Testing Library + Vitest
Teste o comportamento do usuário: preencha campos, envie, afirme que as mensagens de erro aparecem. render(<CheckoutForm />).
2º
Playwright/Cypress E2E
Teste o fluxo completo em um navegador real. Mais lento, mas captura problemas de integração em toda a pilha.
3º
Testes de snapshot
expect(tree).toMatchSnapshot(). Captura mudanças inesperadas, mas não verifica o comportamento.
Escolha errada: Testar detalhes de implementação (estado interno, chamadas de método, classes CSS). Testes quebram a cada refatoração, mesmo quando o comportamento não muda.
Por que a melhor é a melhor: RTL testa o que os usuários veem e fazem -- se o teste passar, o componente funciona. Refatorar internos não quebra testes, então eles realmente mantêm a confiança.
Cenário: Cada página de produto precisa de tags únicas de título, descrição e Open Graph.
Rank
Escolha
Abordagem
Melhor
Função generateMetadata
Exporte async function generateMetadata({ params }) que busca dados do produto e retorna { title, description, openGraph }.
2º
Exportação estática metadata
export const metadata = { title: '...' }. Bom para páginas com metadados fixos.
3º
<Head> de next/head
Apenas Pages Router. Lado do cliente, não funciona com RSC.
Escolha errada: Não definir metadados ou usar o mesmo título em todas as páginas. Motores de busca não conseguem diferenciar suas páginas e compartilhamentos sociais parecem quebrados.
Por que a melhor é a melhor:generateMetadata é executado no servidor, pode buscar dados para gerar títulos dinâmicos e é desduplicado com a busca de dados da página via cache fetch.
Cenário: Usuários enviam avatares de perfil (máximo 5MB) para seu aplicativo.
Rank
Escolha
Abordagem
Melhor
Upload de URL pré-assinado para S3/R2
Server Action gera uma URL pré-assinada. O cliente carrega diretamente no armazenamento. Sem pressão de memória do servidor.
2º
Server Action com FormData
const file = formData.get('avatar'). O servidor recebe o arquivo e o encaminha para o armazenamento. Mais simples, mas limitado pela memória.
3º
API Route com análise estilo multer
Abordagem clássica. Mais controle, mas mais código e configuração.
Escolha errada: Armazenar arquivos carregados no sistema de arquivos do servidor Next.js. Contêineres efêmeros (Vercel, Docker) perdem arquivos na reimplementação. Arquivos devem ir para armazenamento durável.
Por que a melhor é a melhor: URLs pré-assinadas permitem que o cliente carregue diretamente para S3/R2 -- o servidor nunca toca nos bytes, então ele lida com qualquer tamanho de arquivo sem pressão de memória.
Cenário: Seu site de marketing precisa suportar inglês, espanhol e japonês.
Rank
Escolha
Abordagem
Melhor
next-intl com App Router
Middleware detecta o local, grupos de rotas por idioma [locale]/, traduções no lado do servidor.
2º
next-i18next (Pages Router)
Maduro, bem documentado. Melhor opção se você ainda estiver no Pages Router.
3º
Roteamento manual de localidade
Crie seu próprio segmento dinâmico [locale] e carregador de tradução. Controle total, mas reinventa a roda.
Escolha errada: Tradução apenas no lado do cliente (carregando pacotes JSON em useEffect). O conteúdo pisca no idioma padrão antes de mudar, e os motores de busca só veem o padrão.
Por que a melhor é a melhor:next-intl se integra ao middleware e Server Components do App Router, então o conteúdo traduzido está no HTML inicial -- sem flash, SEO completo e detecção automática de localidade.
Cenário: Sua equipe está pronta para implantar um aplicativo Next.js em produção.
Rank
Escolha
Abordagem
Melhor
Vercel
Implantação zero-config, funções de borda, suporte ISR, análises, implantações de pré-visualização por PR.
2º
Auto-hospedado com next start
Execute o servidor de produção Next.js em qualquer máquina que você controle. Controle total sobre a infraestrutura, mas você gerencia tudo sozinho. Veja a explicação abaixo.
3º
Contêiner Docker
Crie uma imagem Docker com saída standalone. Bom para Kubernetes ou infraestrutura de contêiner existente.
Como funciona o auto-hospedado com next start:
Next.js inclui um servidor de produção Node.js embutido. Você compila o aplicativo uma vez e o executa como um processo de longa duração em qualquer servidor com Node.js instalado.
Etapa de compilação - Execute next build em seu CI ou no servidor. Isso compila todas as páginas, gera ativos estáticos e produz o diretório de saída .next/. Server Components são pré-renderizados, pacotes do cliente são otimizados e divididos por código.
Etapa de início - Execute next start (padrão para a porta 3000). Isso inicia um servidor HTTP Node.js que lida com roteamento, SSR, Server Actions, revalidação ISR e middleware -- todos os mesmos recursos que você obtém no Vercel, apenas executados em sua própria máquina.
O que você precisa gerenciar sozinho:
Preocupação
O que Vercel faz por você
O que você faz auto-hospedado
Gerenciamento de processo
Automático
Use PM2 ou systemd para manter o processo ativo e reiniciar em caso de falha
HTTPS / TLS
Automático
Coloque Nginx ou Caddy na frente como um proxy reverso com terminação TLS
Escalabilidade
Auto-escala
Execute várias instâncias atrás de um balanceador de carga (ALB, upstream Nginx)
CDN / ativos estáticos
CDN de borda embutido
Configure CloudFront, Cloudflare ou sirva do Nginx
Cache ISR
Cache distribuído gerenciado
Funciona imediatamente em um único servidor; para multi-servidor, configure um manipulador de cache compartilhado
Variáveis de ambiente
UI do painel
Defina em .env.production, arquivo de unidade systemd ou sua ferramenta de implantação
Implantações sem interrupção
Automático
Implantações blue-green ou rolling através de seus próprios scripts
# Implantação típica auto-hospedada em uma instância EC2/DigitalOcean# 1. Compilar (muitas vezes feito em CI, depois os artefatos são copiados para o servidor)npm cinext build# 2. Iniciar com PM2 para gerenciamento de processopm2 start npm --name "my-app" -- start# ou diretamente:pm2 start node_modules/.bin/next --name "my-app" -- start -p 3000# 3. Proxy reverso Nginx (simplificado)# /etc/nginx/sites-available/my-app# server {# listen 443 ssl;# server_name myapp.com;# location / {# proxy_pass http://localhost:3000;# proxy_http_version 1.1;# proxy_set_header Upgrade $http_upgrade;# proxy_set_header Connection 'upgrade';# proxy_set_header Host $host;# proxy_cache_bypass $http_upgrade;# }# }
Quando escolher auto-hospedado em vez de Vercel: Você precisa permanecer dentro de um provedor de nuvem específico (por exemplo, tudo em AWS VPC), sua política de empresa proíbe hospedagem de terceiros, você precisa de middleware de servidor personalizado (por exemplo, upgrades de WebSocket), ou o custo é uma preocupação em volumes de tráfego altos onde o preço da Vercel excede um servidor dedicado.
Escolha errada: Exportar como HTML estático (next export / output: 'export') quando seu aplicativo usa Server Components, middleware ou ISR. Esses recursos exigem um runtime Node.js -- a exportação estática os descarta silenciosamente.
Por que a melhor é a melhor: Vercel é construído pela equipe do Next.js -- recursos como ISR, middleware e Server Actions funcionam imediatamente sem configuração de infraestrutura. Auto-hospedado é a segunda melhor escolha quando você precisa desse controle de infraestrutura, mas espere possuir o fardo das operações.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026