Fundamentos do Next.js 15

0/26 aulas0%
pratica

Tratamento de Erros, Loading UI e Not Found Pages

Aprenda sobre tratamento de erros, loading ui e not found pages

40 min
Aula 5 de 5

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.tsx deve ser um Client Component (usar 'use client').
  • Ele recebe duas props: error (o objeto de erro) e reset (uma função para tentar renderizar o segmento novamente).
  • Você pode ter múltiplos arquivos error.tsx em diferentes níveis da sua hierarquia de rotas para um controle mais granular.
  • Um error.tsx captura erros em layout.tsx, page.tsx, template.tsx e componentes aninhados.
  • Não captura erros em layout.tsx ou template.tsx do mesmo segmento onde está definido, nem erros em not-found.tsx ou global-error.tsx. Para erros globais, use global-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.

app/dashboard/error.tsx
'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
// 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.tsx pode ser um Server Component.
  • Ele é automaticamente aninhado dentro do layout.tsx do 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
// 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
// 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:

  1. O Next.js não conseguir encontrar uma rota correspondente.
  2. Você chamar a função notFound() de next/navigation em um Server Component.

👉 Pontos Chave:

  • Você pode ter um not-found.tsx na raiz (app/not-found.tsx) para capturar rotas não existentes em toda a aplicação.
  • Você pode ter not-found.tsx em 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.

app/not-found.tsx
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:

  1. Acessar uma rota que não existe (ex: /rota-que-nao-existe).
  2. Usar a função notFound() dentro de um Server Component (como fizemos no exemplo de app/posts/[id]/page.tsx para 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.tsx e app/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.
  • Página de Listagem de Posts (app/posts/page.tsx):
    • Crie uma página que simule a busca de uma lista de posts.
    • Use setTimeout para 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.tsx para 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 por id.
    • Modifique a função fetchPost para que, se o id for 999 (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).
  • Loading UI para Detalhes do Post (app/posts/[id]/loading.tsx):
    • Use o código de exemplo fornecido para criar um loading.tsx para os posts individuais.
  • Tratamento de Erros para Detalhes do Post (app/posts/[id]/error.tsx):
    • Use o código de exemplo fornecido para criar um error.tsx que capture o erro lançado pela função fetchPost.
    • Garanta que ele tenha o botão "Tentar novamente" (reset()).
  • Página Global Not Found (app/not-found.tsx):
    • Use o código de exemplo fornecido para criar uma página not-found.tsx na raiz da pasta app.
    • Teste navegando para uma rota inexistente (ex: /nao-existe).

🛠️ Dicas para o Desafio:

  • Lembre-se de que error.tsx precisa de 'use client'.
  • A função notFound() vem de next/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 como global-error.tsx funciona para capturar erros em layouts raiz e em not-found.tsx.
  • Suspense com loading.tsx: Aprofunde-se em como o loading.tsx se 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! 👋

© 2025 Escola All Dev. Todos os direitos reservados.

Tratamento de Erros, Loading UI e Not Found Pages - Fundamentos do Next.js 15 | escola.all.dev.br