Roteamento eficiente com Next.js: descobrindo o App Router
No mundo frontend, o roteamento é um pedaço crucial do que fazemos. Um pilar, eu diria. Seja com bibliotecas top como o React Router Dom ou até mesmo usando frameworks bacanas como o Next.
Definindo o roteamento
E falando de Next, sabia que ele agora dá a opção de escolher entre dois roteadores? Temos o App Router para as aplicações que estão no ritmo e usam o Next a partir da versão 13, e o Page Router para a galera que tá na versão 12 ou anterior. Vou te contar nesse artigo tudo sobre o App Router: como ele tá funcionando, as ideias chaves e o que ele tem de legal. Bora mergulhar nisso?
Primeiro, antes de mais nada, a gente precisa ficar por dentro dos termos que estão rolando por aí. Então, segura essa lista rápida:
Tree (Árvore): Uma maneira de visualizar uma estrutura hierárquica. Tipo uma árvore de componentes com componentes pais e filhos, uma estrutura de pastas, e por aí vai.
Subtree (Subárvore): Uma parte dessa árvore, começando em uma nova raiz (o primeiro) e terminando nas folhas (os últimos).
Root (Raiz): O primeiro nó em uma árvore ou subárvore, como um layout raiz.
Leaf (Folha): Nós de uma subárvore que não têm filhos, como o último segmento em um caminho de URL.
URL Segment (Segmento de URL): Uma parte do caminho da URL delimitado por barras.
URL Path (Caminho de URL): Aquela parte da URL que vem depois do domínio (e é composta por segmentos).
Com isso em mente, fica mais fácil entender o que vem pela frente. Detalhe: eu não vou usar esses termos traduzidos porque isso pode dificultar o uso da documentação no futuro, tá bem?
O roteador
O App Router foi construído em cima dos Server Side Components (componentes do lado do servidor) do React. Isso significa que agora temos suporte a layouts compartilhados, roteamento aninhado, estados de carregamento, tratamento de erros e muito mais!
Esse roteador funciona em um novo diretório chamado "app" (daí o nome "App Router"). Se você está migrando do Page Router para o App Router, esse diretório "app" trabalha em conjunto com o diretório "pages", permitindo uma adoção gradativa. Assim, você pode incorporar gradualmente as novas funcionalidades em algumas rotas da sua aplicação, enquanto mantém outras rotas no diretório "pages", conforme estava acostumado.
Uma dica importante: O App Router tem prioridade sobre o Page Router. Por isso, tenha atenção para não criar rotas nos dois diretórios que levam ao mesmo caminho URL. Fazendo isso, você vai receber um erro durante a compilação, para evitar conflitos.
Por padrão, os componentes dentro do diretório "app" são Server Components. Isso não só otimiza o desempenho, como também facilita a sua vida na hora de adotá-los. Ah, e não se esqueça: você ainda pode usar Client Components normalmente.
Se você é novo no conceito de Server Components, sugiro dar uma olhada na documentação. Vai te ajudar a entender melhor essa novidade!
Entendendo as funções de pastas e arquivos no Next.js
O Next usa um roteador baseado em sistema de arquivos onde:
Pastas definem as rotas: Imagine as pastas como os caminhos que você segue para chegar a algum lugar. No Next.js, uma rota é como uma trilha formada por pastas aninhadas, que começa na pasta raiz (root) e vai até uma última pasta, que chamaremos de "folha". Dentro dessa pasta "folha", você encontrará um arquivo chamado page.js.
Arquivos são os responsáveis pela aparência: Depois de seguir a trilha (ou seja, a rota), você chega a um destino, certo? No nosso caso, esse destino é o que o usuário vai ver na tela. E quem define essa aparência é o arquivo dentro da pasta. Cada arquivo cria a interface do usuário (UI) para um segmento da rota.
O que é um segmento de Rota?
Imagine cada pasta no seu projeto como um pedacinho do endereço que aparece na barra de navegação do navegador. A cada pasta, acrescentamos um pedaço ao endereço, e chamamos esse pedaço de "segmento de rota". Esse segmento corresponde exatamente ao que você vê na URL.
Rotas aninhadas
Se você quer criar uma rota que tenha várias partes, basta colocar uma pasta dentro da outra. Por exemplo, se quiser que seu site tenha um endereço como "/painel/configurações", é só criar uma pasta "painel" e, dentro dela, uma pasta "configurações" no diretório principal.
Então, para o endereço /painel/configuracoes
, temos:
/
(segmento root, sempre estará lá)painel
(segmento)configuracoes
(o segmento leaf)
Assim, as pastas vão construindo o caminho que vemos na barra de endereços.
Convenções de arquivos no Next.js
Se você está se aventurando pelo Next.js, saiba que ele tem alguns arquivos com nomes bem específicos. Esses arquivos ajudam a criar interfaces com comportamentos diferentes dentro das rotas aninhadas. Aqui vai um resuminho para você:
layout: Define a interface compartilhada para um segmento e seus subsegmentos. Pense nisso como um layout comum que envolve várias páginas.
page: É a estrela do show! Representa a interface única de uma rota e faz com que as rotas sejam acessíveis ao público.
loading: A interface que aparece enquanto uma página ou segmento está carregando.
not-found: A tela que é mostrada quando um segmento ou sua página filha não são encontrados.
error: Mostra erros que acontecem em um segmento específico ou em suas páginas filhas.
global-error: A tela de erro universal, para falhas gerais.
route: Representa um endpoint de API no lado do servidor.
template: É uma versão especial do layout que pode ser re-renderizada com diferentes conteúdos.
default: Uma interface padrão para rotas paralelas.
Dica rápida: Você pode usar as extensões .js, .jsx ou .tsx para esses arquivos especiais.
Hierarquia de componentes
Ao navegar pelo Next.js, é fundamental entender como os componentes React são renderizados em uma ordem específica. Quando falamos dos arquivos especiais de um segmento de rota, a sequência é mais ou menos assim:
layout.js: Pensa nisso como o cenário principal.
template.js: Um tipo de layout, mas que pode ser customizado de diferentes formas.
error.js: Uma espécie de "salva-vidas" para os erros do React.
loading.js: Atua quando o React está "à espera" de algo, graças ao Suspense.
not-found.js: Outro "salva-vidas", mas para quando algo não é encontrado.
page.js ou layout.js aninhados: Aqui temos a estrela principal da rota ou outro cenário que se aninha dentro do principal.
Quando trabalhamos com rotas aninhadas, os componentes de um segmento serão, digamos, "embutidos" dentro dos componentes do segmento pai.
Arquivos não roteados
Além desses arquivos especiais que já conversamos, você ainda tem a opção de agrupar seus próprios arquivos – seja componentes, estilos, testes e assim por diante – dentro das pastas do diretório "app".
E aqui vai um segredinho: apesar das pastas definirem as rotas, somente o conteúdo exibido por page.js
ou route.js
é que pode ser acessado publicamente. Então, fique tranquilo quanto a organizar e colocar seus arquivos lá sem medo de exposição indevida!
Páginas
Uma página é a interface visual que é única para uma rota. Você pode definir páginas exportando um componente de um arquivo chamado page.js. Use pastas aninhadas para definir uma rota e o arquivo page.js para tornar essa rota acessível publicamente.
Dê vida à sua primeira página adicionando um arquivo page.js dentro do diretório app:
// `app/page.tsx` é a interface da URL `/`
export default function Page() {
return <h1>Olá, página inicial!</h1>
}
// `app/dashboard/page.tsx` é a interface da URL `/dashboard`
export default function Page() {
return <h1>Olá, página do painel!</h1>
}
Fique atento:
- Uma página é sempre a "folha" da árvore de rotas.
- As extensões
.js
,.jsx
ou.tsx
podem ser usadas para páginas. - Um arquivo
page.js
é necessário para tornar um segmento de rota publicamente acessível. - As páginas são Server Components por padrão, mas podem ser configuradas como Client Components quando for necessário.
- As páginas podem buscar dados.
Layouts
Um layout é a interface que é compartilhada entre várias páginas. Ao navegar, os layouts mantêm o estado, permanecem interativos e não são re-renderizados. Layouts também podem ser aninhados.
Você pode definir um layout exportando um componente React de um arquivo chamado layout.js. Esse componente deve aceitar uma propriedade children que será preenchida com um layout filho (se existir) ou uma página filha durante a renderização.
export default function LayoutDoPainel({ children }: { children: React.ReactNode }) {
return (
<section>
{/* Inclua as interfaces compartilhada aqui, como um cabeçalho ou barra lateral */}
<nav></nav>
{children}
</section>
)
}
Layout Raiz (Obrigatório)
O layout raiz é definido no nível mais alto do diretório do aplicativo e se aplica a todas as rotas. Esse layout permite que você modifique o HTML inicial retornado pelo servidor.
// app/layout.tsx
export default function LayoutRaiz({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="pt">
<body>{children}</body>
</html>
)
}
Aninhando Layouts
Layouts definidos dentro de uma pasta (por exemplo, app/dashboard/layout.js
) se aplicam a segmentos de rota específicos (por exemplo, alura.com.com.br/dashboard
) e são renderizados quando esses segmentos estão ativos. Por padrão, os layouts na hierarquia de arquivos são aninhados, o que significa que eles envolvem os layouts filhos por meio de sua propriedade children.
Vale saber:
- Apenas o layout raiz pode conter as tags
<html>
e<body>
. - Se você combinasse os dois layouts acima, o layout raiz (
app/layout.js
) envolveria o layout do painel (app/dashboard/layout.js
), que por sua vez envolveria os segmentos de rota dentro deapp/dashboard/*
. Os dois layouts ficariam aninhados assim:
Rotas Dinâmicas
Quando você não conhece os nomes exatos dos segmentos com antecedência e deseja criar rotas a partir de dados dinâmicos, pode usar Segmentos Dinâmicos que são preenchidos no momento da solicitação ou pré-renderizados no momento da construção.
Convenção de nomes
Um Segmento Dinâmico pode ser criado envolvendo o nome de uma pasta em colchetes: [nomeDaPasta]. Por exemplo, [id] ou [nomeFramework].
Os Segmentos Dinâmicos são passados como a propriedade params para as funções layout
, page
, route
e generateMetadata
.
Por exemplo, uma seção de frameworks poderia incluir a seguinte rota app/frameworks/[nomeFramework]/page.js
, onde [nomeFramework] é o Segmento Dinâmico para cada framework.
\\ app/frameworks/[nomeFramework]/page.tsx
export default function Pagina({ params }: { params: { nomeFramework: string } }) {
return <div>Framework: {params.nomeFramework}</div>
}
Rota | URL de Exemplo | params |
---|---|---|
app/frameworks/[nomeFramework]/page.js | /frameworks/react | { nomeFramework: 'react' } |
app/frameworks/[nomeFramework]/page.js | /frameworks/vue | { nomeFramework: 'vue'} |
app/frameworks/[nomeFramework]/page.js | /frameworks/angular | { nomeFramework: 'angular' } |
Lidando com Erros de Forma Amigável
Imagine que você está navegando por um app e, de repente, dá de cara com um erro. Frustrante, né? Para evitar que seus usuários passem por essa experiência, vamos falar sobre a convenção do arquivo error.js. Esse pequeno arquivo nos ajuda a tratar os imprevistos de forma mais elegante durante a execução.
Como ele funciona?
Ele automaticamente envolve uma rota e suas rotas filhas com um "Error Boundary" do React. Caso não conheça, pense nele como um super-herói que contém os erros para que eles não quebrem todo o seu app.
Nos permite criar uma interface visual específica para os erros. Isso significa que, em vez de mostrar uma tela branca assustadora, você mostra uma mensagem amigável ao usuário.
O melhor de tudo é que os erros são isolados, deixando o restante do app funcionando direitinho.
Ah, e tem mais! Você pode adicionar funcionalidades para tentar se recuperar do erro sem que o usuário precise recarregar toda a página.
Colocando em Prática
Vamos criar um exemplo. Dentro de um segmento de rota, adicione o arquivo error.js
e exporte um componente React:
'use client' // Os componentes de erro precisam ser Client Components
import { useEffect } from 'react'
export default function ErroApp({
erroDetectado,
tenteNovamente,
}: {
erroDetectado: Error & { detalhes?: string }
tenteNovamente: () => void
}) {
useEffect(() => {
// Reporte o erro para um serviço de log, se necessário
console.error(erroDetectado)
}, [erroDetectado])
return (
<div>
<h2>Ops! Algo não saiu como esperávamos.</h2>
<button onClick={() => tenteNovamente()}>Tentar novamente</button>
</div>
)
}
Desvendando a Magia por Trás do error.js
O error.js
automaticamente cria um "Error Boundary" do React que envolve segmentos ou componentes.
O componente exportado do error.js é usado como um componente de "fallback" caso ocorra um erro.
Se um erro acontecer, ele é contido e o componente de "fallback" é mostrado. Mas o bacana é que tudo que estiver acima dessa área de erro mantém seu estado e interatividade.
Recuperando-se de Erros
Às vezes, o motivo do erro é algo passageiro. Em alguns casos, só de tentar de novo, o problema já se resolve.
No nosso componente de erro, temos uma função chamada tenteNovamente()
que sugere ao usuário tentar novamente. Quando executada, essa função tenta re-renderizar o conteúdo do Error Boundary. Se tudo correr bem, o componente de erro é substituído pelo resultado da re-renderização.
E aí, legal né? Agora, seus usuários terão uma experiência muito mais amigável em situações não tão agradáveis!
Conclusão
E então, após navegarmos pelas nuances da criação de rotas dinâmicas, entender a estruturação de layouts e, finalmente, explorarmos o tratamento elegante de erros, chegamos ao final desta jornada. Como disse Gandalf em "O Senhor dos Anéis": "Tudo o que temos de decidir é o que fazer com o tempo que nos é dado."
Compreendemos juntos que, em desenvolvimento, o objetivo não é apenas escrever código, mas também criar um impacto. Cada tópico que abordamos é uma peça fundamental desse quebra-cabeça.
Agradeço por ter me acompanhado até aqui. A jornada do aprendizado é contínua. Cada erro, cada nova rota, cada layout é uma oportunidade de crescimento. Continue explorando, codificando e, acima de tudo, divertindo-se nesse universo incrível que é a programação!
Até a próxima aventura no vasto universo geek!