Error Boundary no Next.js: 5 práticas para lidar com erros em runtime

O grupo da equipe de operações entrou em alvoroço: “A página inicial não abre! Só aparece uma tela em branco!”
Ao abrir a plataforma de monitoramento, ficou claro: um componente externo havia falhado e derrubado a página inteira. Para o usuário, restava uma tela completamente branca, sem sequer um aviso de erro. Uma página que funcionava normalmente em produção podia parar de repente por causa de um formato de dados incorreto ou do timeout de uma API. O try-catch tradicional não alcança a etapa de renderização dos componentes React. O resultado é o usuário olhando para a tela vazia antes de fechar a página em silêncio.
Segundo estudos de experiência do usuário, uma página em branco faz mais de 80% dos usuários abandonarem o site.
O Next.js oferece o mecanismo de Error Boundary para lidar com esses erros em runtime de forma controlada. Além de evitar a tela em branco, ele permite exibir uma interface alternativa amigável e até oferecer um botão de nova tentativa para o próprio usuário recuperar a página. A seguir, veremos o uso completo do Error Boundary no Next.js: do error.tsx básico ao global-error.tsx, que funciona como proteção global, além do tratamento específico para Server Components.
Com isso, você poderá fazer a aplicação reagir melhor às falhas e evitar a situação de ser acordado no meio da noite para corrigir um bug.
Por que usar um Error Boundary? Limitações do tratamento tradicional
Quando comecei a usar React, achei que try-catch resolveria qualquer erro. A prática logo mostrou o contrário.
Três limitações importantes do try-catch
A primeira é que ele só captura erros de código síncrono. Se você executar JSON.parse(badData) dentro do bloco try, o erro será capturado. Mas, se a falha ocorrer durante a renderização de um componente, não será.
A segunda aparece nos erros assíncronos de event handlers. Imagine chamar uma API em um evento de clique e a API falhar. O try-catch também não consegue cuidar disso quando o código assíncrono é executado depois que o contexto do bloco já terminou.
A terceira é a mais grave: erros de renderização em componentes React. Se o return do componente acessar uma propriedade de undefined, a página ficará em branco. Nesse caso, o try-catch não ajuda.
Como funciona o React Error Boundary
O React identificou esse problema há bastante tempo e introduziu o mecanismo de Error Boundary. A ideia é simples: a árvore de componentes funciona como um conjunto de caixas aninhadas. O erro se propaga das camadas internas para as externas até encontrar o componente Error Boundary mais próximo.
A abordagem tradicional consiste em criar um componente de classe que implemente os métodos de ciclo de vida componentDidCatch e getDerivedStateFromError. Na prática, ter de escrever um componente de classe toda vez é inconveniente. Além disso, quem já trabalha principalmente com componentes funcionais não consegue usar esses dois métodos diretamente.
A solução do Next.js
Depois que o Next.js 13 introduziu o App Router, o framework passou a encapsular o Error Boundary de uma forma bem mais simples. Basta criar um arquivo error.tsx no diretório da rota, e ele se torna automaticamente o limite de erro dessa rota. Não é necessário criar um componente de classe nem gerenciar o estado por conta própria: o Next.js cuida disso.
Há outro ponto importante: o Error Boundary do Next.js trata erros tanto do servidor quanto do cliente. Se um Server Component falhar durante a renderização no servidor, o error.tsx mais próximo também será acionado. Isso não era possível no React tradicional.
O único cuidado é que o próprio arquivo error.tsx precisa ser um Client Component e começar com a diretiva 'use client'. O motivo é que ele usa hooks do React para tratar o estado de erro e a lógica de recuperação, e hooks só podem ser executados no cliente.
O Facebook Messenger é um exemplo conhecido. A equipe envolveu áreas como barra lateral, janela da conversa e campo de mensagem em Error Boundaries separados. Se uma área falha, as demais continuam funcionando. Talvez o usuário nem perceba que algo deu errado.
Esse é o principal valor de um Error Boundary: impedir que um erro local se transforme em um desastre global.
Como usar error.tsx: um limite de erro local
Vamos à prática. Como escrever um error.tsx?
Estrutura básica: comece em 5 minutos
Crie error.tsx em qualquer diretório de rota e cole este código:
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Envie o erro a uma plataforma de monitoramento, como o Sentry
console.error('Erro capturado:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<h2 className="text-2xl font-bold mb-4">Algo deu errado</h2>
<p className="text-gray-600 mb-4">
{error.message || 'Não foi possível carregar a página'}
</p>
<button
onClick={() => reset()}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Tentar novamente
</button>
</div>
)
}
Três pontos são essenciais:
- Não omita
'use client': essa linha precisa estar no início; caso contrário, o Next.js retornará um erro - Objeto error: contém a mensagem e a stack do erro, além do campo
digest, adicionado no Next.js 15 para rastreamento - Função reset: ao clicar, o conteúdo dentro do limite de erro é renderizado novamente, oferecendo ao usuário uma opção de recuperação
Propagação de erros: subindo nível por nível
Esse mecanismo pode parecer confuso no começo. A estrutura de diretórios abaixo deixa o fluxo mais claro:
app/
├── layout.tsx # Layout raiz
├── error.tsx # Captura erros sob a rota raiz (A)
├── page.tsx # Página inicial
├── dashboard/
│ ├── layout.tsx # Layout do dashboard
│ ├── error.tsx # Captura erros sob dashboard (B)
│ └── page.tsx # Página do dashboard
└── profile/
└── page.tsx # Página de perfil
Se ocorrer um erro durante a renderização de dashboard/page.tsx, quem o captura? A resposta é (B), o error.tsx pai mais próximo.
E se o erro estiver em profile/page.tsx? Como não há error.tsx no diretório profile, o erro continua subindo e é capturado por (A).
Atenção a uma armadilha: error.tsx não captura erros do layout.tsx no mesmo nível. O limite de erro fica dentro do layout; se o layout falhar, o limite ainda nem foi carregado. Para capturar um erro em dashboard/layout.tsx, é preciso tratá-lo em app/error.tsx.
Como usar reset() corretamente
A função reset pode parecer especial, mas ela simplesmente renderiza novamente a subárvore do componente que falhou. Ela é adequada para erros temporários, como:
- Timeout em uma requisição de API, quando uma nova tentativa pode funcionar
- Oscilação de rede que impede o carregamento de um recurso
- Uma condição-limite acionada pela entrada do usuário
Se for um bug no código, como acessar undefined.property, nenhuma quantidade de tentativas resolverá. Nesse caso, é preciso identificar o erro na plataforma de monitoramento, corrigir o código e implantar uma nova versão.
Algumas equipes adicionam um contador à lógica de reset. Depois de três falhas, o botão de nova tentativa deixa de aparecer, e a interface orienta o usuário a atualizar a página ou entrar em contato com o suporte. É uma solução prática:
'use client'
import { useEffect, useState } from 'react'
export default function Error({ error, reset }: {
error: Error & { digest?: string }
reset: () => void
}) {
const [retryCount, setRetryCount] = useState(0)
const handleReset = () => {
setRetryCount(prev => prev + 1)
reset()
}
return (
<div>
<h2>Ocorreu um erro</h2>
{retryCount < 3 ? (
<button onClick={handleReset}>
Tentar novamente ({retryCount}/3)
</button>
) : (
<p>As tentativas falharam. Atualize a página ou <a href="/contact">fale conosco</a></p>
)}
</div>
)
}
global-error.tsx: proteção global contra erros
O error.tsx é muito útil, mas deixa uma lacuna: ele não captura erros no layout raiz app/layout.tsx. É aí que entra o global-error.tsx.
Quando usar global-error.tsx?
Na prática, esse arquivo raramente é acionado em produção. Ele serve principalmente para dois cenários graves:
- Falha na inicialização do
layout.tsxraiz, por exemplo quando a biblioteca global de gerenciamento de estado para de funcionar - Erros que não foram capturados por nenhum
error.tsx
Pense nele como a última rede de segurança: você espera nunca precisar dela, mas ela deve existir.
O que torna global-error.tsx diferente
Em comparação com um error.tsx comum, o global-error.tsx tem uma diferença essencial: ele precisa incluir a estrutura HTML completa, com as tags <html> e <body>.
Isso acontece porque ele substitui completamente o layout.tsx raiz. Quando o layout raiz falha, toda a estrutura da página desaparece, e global-error.tsx precisa construir do zero uma página mínima que ainda funcione.
O código completo fica assim:
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
padding: '20px',
fontFamily: 'system-ui, sans-serif'
}}>
<h1>A aplicação encontrou um problema grave</h1>
<p style={{ color: '#666', marginBottom: '20px' }}>
{process.env.NODE_ENV === 'development'
? error.message
: 'Estamos cuidando do problema. Tente novamente mais tarde'}
</p>
<button
onClick={() => reset()}
style={{
padding: '10px 20px',
background: '#0070f3',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
Recarregar a aplicação
</button>
</div>
</body>
</html>
)
}
Observe que usei estilos inline, em vez de Tailwind ou CSS Modules. O motivo é simples: nesse momento, talvez o sistema de estilos ainda nem tenha sido carregado. A solução mais básica garante que a página continue legível.
Ambiente de desenvolvimento versus produção
Um detalhe importante: global-error.tsx só entra em ação no ambiente de produção. Em desenvolvimento, o Next.js continua mostrando a tela vermelha com a stack do erro, o que facilita a depuração.
Em produção, recomendo ocultar informações técnicas e mostrar ao usuário apenas uma mensagem amigável. No código acima, a verificação de process.env.NODE_ENV faz exatamente isso. O usuário não se importa com TypeError: Cannot read property 'map' of undefined; ele quer saber se ainda pode usar a aplicação e quando o problema será resolvido.
Vale a pena adicionar global-error.tsx?
Minha recomendação é adicionar. A probabilidade de esse arquivo ser acionado é baixa, mas, quando isso acontece, trata-se de um incidente grave. Com essa proteção, o usuário ao menos verá uma página de erro adequada, em vez da mensagem padrão do navegador informando que o site não pode ser acessado.
É como contratar um seguro: você não espera ter um problema, mas é melhor contar com a proteção quando ele ocorre.
Cuidados específicos com erros em Server Components
Os Server Components do Next.js 13+ trouxeram novos desafios ao tratamento de erros. Erros do servidor e do cliente são tratados de maneiras diferentes.
Para onde vão os erros dos Server Components?
No início, os Server Components podem causar dúvida. Se um componente é renderizado no servidor e ocorre uma falha, o error.tsx do cliente consegue capturá-la?
A resposta é sim. O Next.js envia as informações do erro do servidor ao cliente e aciona o error.tsx mais próximo. Há, porém, um mecanismo de segurança importante: em produção, as informações do erro são ocultadas para evitar o vazamento de dados sensíveis do servidor.
Por exemplo, se a conexão com o banco de dados falhar, o ambiente de desenvolvimento exibirá a stack completa. Em produção, o usuário verá apenas uma mensagem genérica, como “Falha ao carregar”.
Erros esperados versus erros inesperados
Essa distinção é importante e recebe destaque na documentação oficial. Há dois tipos de erro:
Erros esperados: fazem parte da lógica de negócio e devem ser tratados explicitamente
- Falha na validação de um formulário, quando a entrada do usuário está em formato incorreto
- Resposta 404 da API, quando os dados não existem
- Falta de permissão, quando o usuário não está autenticado
Erros inesperados: são bugs no código ou falhas no nível do sistema e devem ser encaminhados ao Error Boundary
- Falha na conexão com o banco de dados
- Indisponibilidade de um serviço externo
- Tentativa do código de acessar uma propriedade de
undefined
Para erros esperados, use try-catch em um Server Action ou na função de busca de dados e retorne a mensagem de erro ao componente:
// app/actions.ts
'use server'
export async function createUser(formData: FormData) {
const email = formData.get('email') as string
// Erro esperado: formato de e-mail inválido
if (!email.includes('@')) {
return { error: 'Digite um endereço de e-mail válido' }
}
try {
await db.user.create({ email })
return { success: true }
} catch (error) {
// Erro inesperado: falha no banco; lance o erro para o Error Boundary
throw new Error('Não foi possível criar o usuário')
}
}
Para erros inesperados, use throw diretamente e deixe o erro se propagar até o error.tsx mais próximo.
Tratamento de erros na busca de dados
Em Server Components, costumo tratar a busca de dados desta forma:
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
// Erro esperado: a API retornou um status de erro
if (!res.ok) {
// Decida conforme o tipo de erro entre tratamento explícito e lançamento
if (res.status === 404) {
return { posts: [], error: 'Nenhum dado disponível' }
}
// Erro do servidor: lance para o Error Boundary
throw new Error('Não foi possível buscar os dados')
}
return { posts: await res.json() }
}
export default async function PostsPage() {
const { posts, error } = await getPosts()
// Renderize explicitamente o estado de erro
if (error) {
return <div>Nenhum artigo disponível</div>
}
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
)
}
A vantagem é oferecer uma experiência mais amigável. A mensagem “Nenhum dado disponível” não precisa acionar a página de erro; somente uma falha real do sistema deve exibir a interface alternativa de error.tsx.
Como aproveitar error.digest
O Next.js 15 adicionou o campo digest ao objeto error. Trata-se de um identificador único gerado automaticamente.
Para que ele serve? Imagine que um usuário vê a página de erro, tira uma captura e envia ao suporte dizendo que a página não abre. Com o digest, a equipe de atendimento consegue consultar os logs e identificar exatamente qual requisição falhou, em que momento e com qual erro.
Em error.tsx, ele pode ser usado assim:
'use client'
export default function Error({ error }: { error: Error & { digest?: string }}) {
return (
<div>
<h2>Ocorreu um erro</h2>
<p>Código do erro: {error.digest}</p>
<p>Entre em contato com o suporte e informe o código acima</p>
</div>
)
}
Com o Sentry ou outra plataforma de monitoramento, o digest torna o rastreamento de erros muito mais eficiente.
Boas práticas para o ambiente de produção
Já vimos como usar o mecanismo. Agora, vale entender como aplicá-lo bem. Estas são práticas que adotei depois de enfrentar alguns problemas.
1. Crie limites de erro granulares
Não coloque apenas um error.tsx no diretório raiz e considere o trabalho encerrado. Áreas importantes da aplicação devem ter seus próprios limites de erro.
Em um e-commerce, por exemplo, a estrutura pode ser esta:
app/
├── error.tsx # Proteção geral
├── (shop)/
│ ├── products/
│ │ └── error.tsx # Um erro na lista de produtos não afeta outras áreas
│ ├── cart/
│ │ └── error.tsx # Um erro no carrinho não impede a navegação pelos produtos
│ └── checkout/
│ └── error.tsx # O checkout é crítico e recebe tratamento separado
Assim, mesmo que o componente do carrinho falhe, o usuário ainda pode navegar pelos produtos. O site inteiro não fica indisponível.
2. Monitore e envie os erros
O useEffect de error.tsx é o momento ideal para enviar o erro:
'use client'
import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'
export default function Error({ error, reset }: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Envie ao Sentry
Sentry.captureException(error, {
tags: {
errorDigest: error.digest,
errorBoundary: 'app-root'
},
extra: {
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
}
})
}, [error])
return (
// Interface de erro...
)
}
Inclua error.digest e as informações do ambiente do usuário para facilitar a reprodução do problema.
Algumas equipes também registram o caminho das últimas ações do usuário, como as cinco páginas visitadas antes do erro. Isso ajuda bastante na investigação.
3. Crie uma interface de erro amigável
Profissionais de tecnologia gostam de analisar a stack, mas o usuário não precisa desses detalhes. Ele quer saber:
- O que aconteceu, em linguagem simples?
- É possível resolver, e qual ação deve ser tomada?
- Meus dados foram perdidos, e qual é o impacto?
Uma boa interface de erro pode ser assim:
return (
<div className="error-container">
<h2>Não foi possível carregar a página</h2>
<p>A rede pode estar instável ou nossos servidores podem estar descansando</p>
<div className="actions">
<button onClick={reset}>Tentar novamente</button>
<a href="/">Voltar à página inicial</a>
<a href="/help">Falar com o suporte</a>
</div>
<details className="error-details">
<summary>Informações técnicas (opcional)</summary>
<code>{error.digest}</code>
</details>
</div>
)
Use um tom leve sem aumentar a ansiedade do usuário. “Nossos servidores podem estar descansando” é mais amigável do que “500 Internal Server Error”.
4. Use uma estratégia inteligente de novas tentativas
Já falamos sobre limitar a quantidade de tentativas. Há outros cuidados úteis:
- Atraso antes da nova tentativa: não execute
resetimediatamente; espere de 1 a 2 segundos para dar tempo ao servidor - Backoff exponencial: espere 1 segundo na primeira vez, 2 na segunda e 4 na terceira
- Tratamento conforme o tipo de erro: sugira uma nova tentativa para erros de rede e, em erros de código, oriente o usuário a contatar o suporte técnico
const [retryCount, setRetryCount] = useState(0)
const [isRetrying, setIsRetrying] = useState(false)
const handleReset = async () => {
setIsRetrying(true)
setRetryCount(prev => prev + 1)
// Backoff exponencial: 2^retryCount segundos
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, retryCount) * 1000)
)
setIsRetrying(false)
reset()
}
5. Trate os ambientes de forma diferente
A apresentação dos erros deve ser diferente em desenvolvimento e produção:
const isDev = process.env.NODE_ENV === 'development'
return (
<div>
<h2>{isDev ? error.message : 'Algo deu errado'}</h2>
{isDev && (
<pre>
<code>{error.stack}</code>
</pre>
)}
{!isDev && (
<p>Já registramos o problema e vamos corrigi-lo o quanto antes</p>
)}
</div>
)
Em desenvolvimento, exiba a stack completa para facilitar a depuração. Em produção, mostre apenas uma mensagem amigável e não revele detalhes técnicos.
6. Não use Error Boundaries em excesso
Por fim, lembre-se de que o Error Boundary é uma proteção final, não o principal mecanismo de tratamento de erros.
Erros esperados que podem ser tratados com try-catch não devem ser encaminhados ao Error Boundary. Quando um componente pode aplicar uma degradação controlada internamente, não é necessário acionar a página de erro.
Se a imagem do perfil não carregar, por exemplo, basta mostrar um avatar padrão. Não é preciso derrubar toda a página do perfil.
Reserve o Error Boundary para erros realmente inesperados que não podem ser tratados de forma local.
Conclusão
Depois de todos esses detalhes, três pontos resumem o essencial:
Primeiro, Error Boundaries não são opcionais: são necessários. A perda de usuários causada por uma página em branco pode ser maior do que parece. Investir algum tempo na configuração dos limites de erro evita muitos chamados durante a madrugada.
Segundo, o tratamento em camadas é fundamental. error.tsx cuida de erros locais, global-error.tsx funciona como proteção global e, nos Server Components, é preciso distinguir erros esperados de erros inesperados. Trate explicitamente o que for previsível e deixe o restante chegar ao limite de erro.
Terceiro, priorize a experiência do usuário. Deixe os detalhes técnicos na plataforma de monitoramento e mostre ao usuário mensagens amigáveis com ações claras. Um botão de nova tentativa resolve 40% dos erros temporários, oferecendo um bom retorno para uma implementação simples.
Adicione agora um error.tsx ao seu projeto Next.js. Comece pelo diretório raiz e, depois, crie limites de erro nas áreas mais importantes. Com uma ferramenta de monitoramento como o Sentry, a melhora na estabilidade da aplicação se torna evidente.
E não se esqueça de global-error.tsx. Ele raramente será acionado, mas funciona como um cinto de segurança: você espera não precisar dele, porém ele deve estar lá.
Implementar um Error Boundary no Next.js
Adicione limites de erro a uma aplicação Next.js para lidar com falhas em runtime de forma controlada
- 1
Step 1: Criar o arquivo error.tsx
Crie um arquivo error.tsx no diretório app ou em qualquer diretório de rota e adicione a diretiva 'use client' - 2
Step 2: Implementar o componente de tratamento de erro
Defina um componente Error que receba os parâmetros error e reset e crie uma interface de erro fácil de entender - 3
Step 3: Adicionar o envio de erros
Use useEffect para enviar o erro a uma plataforma de monitoramento, como o Sentry, e registre error.digest - 4
Step 4: Implementar novas tentativas inteligentes
Adicione um botão de nova tentativa, limite a quantidade de tentativas e ofereça recuperação automática para erros temporários - 5
Step 5: Criar global-error.tsx
Crie global-error.tsx no diretório app como última camada de proteção, incluindo a estrutura HTML completa - 6
Step 6: Diferenciar os tipos de erro
Em Server Components, trate erros esperados explicitamente e encaminhe erros inesperados ao Error Boundary
FAQ
Qual é a diferença entre error.tsx e global-error.tsx?
Por que error.tsx precisa ser um Client Component?
Erros de Server Components podem ser capturados por error.tsx?
Quando usar try-catch em vez de um Error Boundary?
Como a função reset() funciona?
17 min de leitura · Publicado em: 6 jan 2026 · Atualizado em: 4 set 2026
Guia completo Next.js
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Configuração profissional no Next.js: ESLint + Prettier + Husky do zero
Seu PR foi devolvido na sexta à noite por problemas de formatação? O estilo inconsistente do time gera conflitos sem sentido? Este guia mostra como configurar ESLint, Prettier e Husky para automatizar verificações e formatação e tornar a colaboração mais eficiente.
Parte 18 de 26
Próximo
Testes unitários no Next.js: guia completo com Jest e React Testing Library
Configure do zero um ambiente de testes para Next.js 15 com Jest e React Testing Library, incluindo testes de Client e Server Components, Hooks, técnicas de Mock e solução de problemas, com exemplos completos de código.
Parte 20 de 26



Comentários
Entre com GitHub para comentar