Fundamentos do Next.js 15
Tratamento de Erros, Loading UI e Not Found Pages
Aprenda sobre tratamento de erros, loading ui e not found pages
Tratamento de Erros, Loading UI e Not Found Pages no Next.js 15
Olá, futuros desenvolvedores Next.js! 👋 Nesta aula prática, vamos mergulhar em três pilares essenciais para construir aplicações robustas e com uma excelente experiência de usuário: o tratamento de erros, as telas de carregamento (Loading UI) e as páginas de "não encontrado" (Not Found Pages).
No universo do desenvolvimento web, nem tudo sai como planejado. Conexões de rede falham, dados não são encontrados, ou o servidor pode demorar para responder. O Next.js 15, com seu App Router, nos oferece mecanismos poderosos e padronizados para lidar com esses cenários de forma elegante, garantindo que nossos usuários nunca se deparem com uma tela em branco ou uma mensagem de erro genérica.
Vamos colocar a mão na massa e aprender a implementar cada um desses recursos! 🚀
1. Introdução: A Importância da Resiliência na UI
Imagine um usuário acessando seu aplicativo:
- Ele clica em um link e a página demora para carregar. Sem um indicador, ele pode pensar que a página travou e desistir. (Solução: Loading UI)
- Ele tenta acessar um produto que foi excluído. Sem uma página dedicada, ele pode ver um erro feio ou ser jogado para a página inicial sem contexto. (Solução: Not Found Pages)
- Durante uma operação, algo inesperado acontece no servidor. Em vez de uma falha total, você pode apresentar uma mensagem amigável e uma opção para tentar novamente. (Solução: Tratamento de Erros)
Estes são os problemas que vamos resolver hoje, usando os recursos nativos do Next.js 15.
2. Tratamento de Erros com error.tsx
O Next.js permite que você defina limites de erro (Error Boundaries) a nível de segmento de rota usando o arquivo error.tsx. Isso isola erros para uma parte específica da sua aplicação, mantendo o restante funcional.
Como funciona?
Quando ocorre um erro em um segmento de rota ou em seus filhos, o Next.js renderizará o componente error.tsx desse segmento, substituindo o conteúdo que falhou.
👉 Pontos Chave:
error.tsxdeve ser um Client Component (usar'use client').- Ele recebe duas props:
error(o objeto de erro) ereset(uma função para tentar renderizar o segmento novamente). - Você pode ter múltiplos arquivos
error.tsxem diferentes níveis da sua hierarquia de rotas para um controle mais granular. - Um
error.tsxcaptura erros emlayout.tsx,page.tsx,template.tsxe componentes aninhados. - Não captura erros em
layout.tsxoutemplate.tsxdo mesmo segmento onde está definido, nem erros emnot-found.tsxouglobal-error.tsx. Para erros globais, useglobal-error.tsx(que é um pouco diferente e fora do escopo desta aula, mas bom saber).
Exemplo de Código (error.tsx)
Vamos criar um cenário onde um componente pode falhar.
'use client'; // Error components must be Client Components
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-[50vh] bg-red-50 p-6 rounded-lg shadow-md">
<h2 className="text-2xl font-bold text-red-800 mb-4">
Oops! Algo deu errado no Dashboard! 😟
</h2>
<p className="text-gray-700 mb-6 text-center">
Parece que encontramos um problema inesperado. Por favor, tente novamente.
</p>
<button
className="px-6 py-3 bg-red-600 text-white font-semibold rounded-lg shadow-lg hover:bg-red-700 transition duration-300 ease-in-out"
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Tentar novamente
</button>
<p className="mt-4 text-sm text-red-600">
Detalhes do erro (apenas para depuração): {error.message}
</p>
</div>
);
}Para testar, você precisaria de um componente que lança um erro:
// app/dashboard/page.tsx
'use client'; // Para simular um erro no cliente facilmente
import { useState } from 'react';
export default function DashboardPage() {
const [shouldError, setShouldError] = useState(false);
if (shouldError) {
throw new Error('Erro simulado no componente Dashboard!');
}
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-6">Página do Dashboard</h1>
<p className="text-lg mb-4">
Bem-vindo ao seu dashboard. Tudo funcionando perfeitamente (por enquanto)!
</p>
<button
onClick={() => setShouldError(true)}
className="px-5 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 transition"
>
Simular Erro
</button>
</div>
);
}Quando você clicar no botão "Simular Erro" em /dashboard, o error.tsx será renderizado.
3. Loading UI com loading.tsx
As telas de carregamento são cruciais para a experiência do usuário. Elas informam ao usuário que algo está acontecendo e que a página não está travada.
Como funciona?
O Next.js permite criar um estado de carregamento instantâneo com loading.tsx. Este arquivo é renderizado imediatamente quando um novo segmento é carregado, enquanto o conteúdo do segmento (incluindo page.tsx e qualquer layout.tsx aninhado) está sendo buscado e renderizado no servidor.
👉 Pontos Chave:
loading.tsxpode ser um Server Component.- Ele é automaticamente aninhado dentro do
layout.tsxdo mesmo segmento. - Você pode usar esqueletos de UI, spinners ou qualquer indicador visual.
- É especialmente útil para dados que demoram a carregar.
Exemplo de Código (loading.tsx)
Vamos criar um loading.tsx simples para um segmento de posts.
// app/posts/[id]/loading.tsx
export default function Loading() {
return (
<div className="flex flex-col items-center justify-center min-h-[70vh] bg-blue-50 p-6 rounded-lg shadow-md animate-pulse">
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mb-4"></div>
<h2 className="text-xl font-semibold text-blue-800 mb-2">Carregando Post...</h2>
<p className="text-gray-600">Buscando os detalhes do artigo. Um momento, por favor!</p>
{/* Skeleton UI para o conteúdo */}
<div className="mt-8 w-full max-w-2xl">
<div className="h-8 bg-gray-200 rounded w-3/4 mb-4"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-5/6 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
</div>
);
}Para testar, você precisaria de uma página que simula um carregamento demorado:
// app/posts/[id]/page.tsx
import { notFound } from 'next/navigation';
interface PostPageProps {
params: { id: string };
}
// Função para simular busca de dados com atraso
async function fetchPost(id: string) {
// Simula um atraso de 2 segundos
await new Promise(resolve => setTimeout(resolve, 2000));
const posts = [
{ id: '1', title: 'Primeiro Post', content: 'Conteúdo do primeiro post...' },
{ id: '2', title: 'Segundo Post', content: 'Conteúdo do segundo post, mais detalhado.' },
];
const post = posts.find(p => p.id === id);
if (!post) {
// Se o post não for encontrado, dispara a página not-found.tsx
notFound();
}
return post;
}
export default async function PostPage({ params }: PostPageProps) {
const post = await fetchPost(params.id);
return (
<div className="p-8 max-w-3xl mx-auto">
<h1 className="text-4xl font-extrabold text-gray-900 mb-6">{post.title}</h1>
<p className="text-gray-700 leading-relaxed text-lg">{post.content}</p>
<p className="mt-8 text-sm text-gray-500">ID do Post: {post.id}</p>
</div>
);
}Ao navegar para /posts/1 ou /posts/2, você verá o loading.tsx por 2 segundos antes do conteúdo do post aparecer.
4. Not Found Pages com not-found.tsx
Quando um recurso não existe ou uma rota não é encontrada, é fundamental guiar o usuário com uma mensagem clara e opções de navegação.
Como funciona?
O Next.js permite criar uma página personalizada de "não encontrado" usando not-found.tsx. Esta página será exibida quando:
- O Next.js não conseguir encontrar uma rota correspondente.
- Você chamar a função
notFound()denext/navigationem um Server Component.
👉 Pontos Chave:
- Você pode ter um
not-found.tsxna raiz (app/not-found.tsx) para capturar rotas não existentes em toda a aplicação. - Você pode ter
not-found.tsxem segmentos específicos para lidar com recursos não encontrados dentro daquele escopo. - A função
notFound()é um Server Component utility e deve ser chamada dentro de um Server Component ou em um Client Component que faz uma requisição a um Server Action ou Route Handler.
Exemplo de Código (not-found.tsx)
Vamos criar uma página de "não encontrado" genérica para a aplicação.
import Link from 'next/link';
export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center min-h-[80vh] bg-gray-100 p-8 text-center">
<h2 className="text-6xl font-extrabold text-gray-800 mb-4">404</h2>
<h3 className="text-3xl font-semibold text-gray-700 mb-6">Página Não Encontrada 🧐</h3>
<p className="text-lg text-gray-600 mb-8">
Ops! Parece que a página que você está procurando não existe.
</p>
<Link href="/" className="px-7 py-3 bg-blue-600 text-white font-semibold rounded-lg shadow-lg hover:bg-blue-700 transition duration-300 ease-in-out">
Voltar para a Página Inicial
</Link>
</div>
);
}Para testar, você pode:
- Acessar uma rota que não existe (ex:
/rota-que-nao-existe). - Usar a função
notFound()dentro de um Server Component (como fizemos no exemplo deapp/posts/[id]/page.tsxpara posts inexistentes).
5. Exercícios Práticos: Construindo um Mini-Blog Resiliente
Vamos aplicar o que aprendemos para criar um pequeno sistema de posts.
🎯 Desafio: Implementar um Sistema de Posts com Resiliência
Objetivo: Criar um sistema de exibição de posts que lide com estados de carregamento, erros e posts não encontrados.
Estrutura do Projeto:
app/
├── layout.tsx
├── page.tsx
├── not-found.tsx // Página 404 global
├── posts/
│ ├── [id]/
│ │ ├── page.tsx
│ │ ├── loading.tsx // Loading UI para posts individuais
│ │ └── error.tsx // Tratamento de erro para posts individuais
│ └── page.tsx // Página que lista todos os posts
Tarefas:
- Configuração Inicial:
- Crie o projeto Next.js 15 (se ainda não o fez).
- Remova o conteúdo padrão de
app/page.tsxeapp/layout.tsx, deixando-os mínimos.
- Página Inicial (
app/page.tsx):- Crie uma página inicial simples com um título e um link para
/posts.
- Crie uma página inicial simples com um título e um link para
- Página de Listagem de Posts (
app/posts/page.tsx):- Crie uma página que simule a busca de uma lista de posts.
- Use
setTimeoutpara simular um atraso de 1 a 2 segundos na busca. - Exiba uma lista de links para posts individuais (ex:
/posts/1,/posts/2,/posts/3). - Adicional: Implemente um
loading.tsxpara esta página de listagem (app/posts/loading.tsx).
- Página de Detalhes do Post (
app/posts/[id]/page.tsx):- Use o código de exemplo fornecido anteriormente (
app/posts/[id]/page.tsx) que busca um post porid. - Modifique a função
fetchPostpara que, se oidfor999(ou qualquer outro ID específico que você definir), ela lance um erro (ex:throw new Error('Falha na conexão com o banco de dados de posts!');). - Certifique-se de que a função
notFound()seja chamada se o post não for encontrado (ex: IDs 1, 2, 3 existem, 4 não existe).
- Use o código de exemplo fornecido anteriormente (
- Loading UI para Detalhes do Post (
app/posts/[id]/loading.tsx):- Use o código de exemplo fornecido para criar um
loading.tsxpara os posts individuais.
- Use o código de exemplo fornecido para criar um
- Tratamento de Erros para Detalhes do Post (
app/posts/[id]/error.tsx):- Use o código de exemplo fornecido para criar um
error.tsxque capture o erro lançado pela funçãofetchPost. - Garanta que ele tenha o botão "Tentar novamente" (
reset()).
- Use o código de exemplo fornecido para criar um
- Página Global Not Found (
app/not-found.tsx):- Use o código de exemplo fornecido para criar uma página
not-found.tsxna raiz da pastaapp. - Teste navegando para uma rota inexistente (ex:
/nao-existe).
- Use o código de exemplo fornecido para criar uma página
🛠️ Dicas para o Desafio:
- Lembre-se de que
error.tsxprecisa de'use client'. - A função
notFound()vem denext/navigation. - Para simular atrasos,
await new Promise(resolve => setTimeout(resolve, tempo))é seu amigo. - Para simular erros,
throw new Error('Sua mensagem de erro');é o suficiente.
6. Resumo e Próximos Passos
Parabéns! 🎉 Você dominou os fundamentos de como construir UIs mais resilientes e amigáveis no Next.js 15.
error.tsx: Permite criar limites de erro para segmentos de rota, isolando falhas e oferecendo uma experiência de recuperação.loading.tsx: Oferece um feedback instantâneo ao usuário durante o carregamento de dados, melhorando a percepção de velocidade.not-found.tsx: Garante que usuários perdidos sejam gentilmente guiados de volta ao caminho certo, seja por uma rota inexistente ou um recurso não encontrado.
Esses recursos são cruciais para qualquer aplicação de nível de produção, transformando potenciais pontos de frustração em oportunidades para guiar e informar o usuário.
⏭️ Próximos Passos:
- Global Error Handling (
global-error.tsx): Explore a documentação do Next.js para entender comoglobal-error.tsxfunciona para capturar erros em layouts raiz e emnot-found.tsx. - Suspense com
loading.tsx: Aprofunde-se em como oloading.tsxse integra com o React Suspense para streaming de HTML e como ele pode ser usado com componentes individuais. - Estratégias de Cache e Revalidação: Entenda como o tratamento de erros e loading UI se relacionam com as estratégias de cache do Next.js para dados.
Continue praticando e explorando! A resiliência da sua aplicação é tão importante quanto suas funcionalidades. Até a próxima aula! 👋