Português

Desbloqueie o poder do App Router do Next.js com nosso guia aprofundado sobre roteamento baseado em arquivos. Aprenda a estruturar sua aplicação, criar rotas dinâmicas, gerenciar layouts e muito mais.

App Router do Next.js: Um Guia Abrangente para Roteamento Baseado em Arquivos

O App Router do Next.js, introduzido no Next.js 13 e tornado padrão em versões posteriores, revoluciona a forma como estruturamos e navegamos em aplicações. Ele introduz um sistema de roteamento baseado em arquivos poderoso e intuitivo que simplifica o desenvolvimento, melhora o desempenho e aprimora a experiência geral do desenvolvedor. Este guia abrangente aprofundará o roteamento baseado em arquivos do App Router, fornecendo o conhecimento e as habilidades para construir aplicações Next.js robustas e escaláveis.

O que é Roteamento Baseado em Arquivos?

Roteamento baseado em arquivos é um sistema de roteamento onde a estrutura das rotas da sua aplicação é diretamente determinada pela organização dos seus arquivos e diretórios. No App Router do Next.js, você define rotas criando arquivos dentro do diretório `app`. Cada pasta representa um segmento de rota, e arquivos especiais dentro dessas pastas definem como esse segmento de rota será tratado. Essa abordagem oferece várias vantagens:

Começando com o App Router

Para usar o App Router, você precisa criar um novo projeto Next.js ou migrar um projeto existente. Certifique-se de que está usando a versão 13 ou posterior do Next.js.

Criando um Novo Projeto:

Você pode criar um novo projeto Next.js com o App Router usando o seguinte comando:

npx create-next-app@latest my-app --example with-app

Migrando um Projeto Existente:

Para migrar um projeto existente, você precisa mover suas páginas do diretório `pages` para o diretório `app`. Pode ser necessário ajustar sua lógica de roteamento de acordo. O Next.js fornece um guia de migração para ajudá-lo com este processo.

Conceitos Essenciais de Roteamento Baseado em Arquivos

O App Router introduz vários arquivos e convenções especiais que definem como suas rotas são tratadas:

1. O Diretório `app`

O diretório `app` é a raiz das rotas da sua aplicação. Todos os arquivos e pastas dentro deste diretório serão usados para gerar rotas. Qualquer coisa fora do diretório `app` (como o diretório `pages` se você estiver migrando) será ignorada pelo App Router.

2. O Arquivo `page.js`

O arquivo `page.js` (ou `page.jsx`, `page.ts`, `page.tsx`) é a parte mais fundamental do App Router. Ele define o componente de UI que será renderizado para um segmento de rota específico. É um arquivo obrigatório para qualquer segmento de rota que você queira que seja diretamente acessível.

Exemplo:

Se você tiver uma estrutura de arquivos como esta:

app/
  about/
    page.js

O componente exportado de `app/about/page.js` será renderizado quando um usuário navegar para `/about`.

// app/about/page.js
import React from 'react';

export default function AboutPage() {
  return (
    <div>
      <h1>Sobre Nós</h1>
      <p>Saiba mais sobre nossa empresa.</p>
    </div>
  );
}

3. O Arquivo `layout.js`

O arquivo `layout.js` (ou `layout.jsx`, `layout.ts`, `layout.tsx`) define uma UI que é compartilhada entre várias páginas dentro de um segmento de rota. Layouts são úteis para criar cabeçalhos, rodapés, barras laterais e outros elementos consistentes que devem estar presentes em várias páginas.

Exemplo:

Digamos que você queira adicionar um cabeçalho tanto para a página `/about` quanto para uma hipotética página `/about/team`. Você pode criar um arquivo `layout.js` no diretório `app/about`:

// app/about/layout.js
import React from 'react';

export default function AboutLayout({ children }) {
  return (
    <div>
      <header>
        <h1>Sobre a Nossa Empresa</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

A prop `children` será substituída pela UI renderizada pelo arquivo `page.js` no mesmo diretório ou em quaisquer diretórios aninhados.

4. O Arquivo `template.js`

O arquivo `template.js` é semelhante ao `layout.js`, mas cria uma nova instância do componente para cada rota filha. Isso é útil para cenários onde você deseja manter o estado do componente ou evitar re-renderizações ao navegar entre rotas filhas. Diferente dos layouts, os templates serão re-renderizados na navegação. Usar templates é ótimo para animar elementos na navegação.

Exemplo:

// app/template.js
'use client'

import { useState } from 'react'

export default function Template({ children }) {
  const [count, setCount] = useState(0)

  return (
    <main>
      <p>Template: {count}</p>
      <button onClick={() => setCount(count + 1)}>Atualizar Template</button>
      {children}
    </main>
  )
}

5. O Arquivo `loading.js`

O arquivo `loading.js` (ou `loading.jsx`, `loading.ts`, `loading.tsx`) permite que você crie uma UI de carregamento que é exibida enquanto um segmento de rota está carregando. Isso é útil para fornecer uma melhor experiência do usuário ao buscar dados ou realizar outras operações assíncronas.

Exemplo:

// app/about/loading.js
import React from 'react';

export default function Loading() {
  return <p>Carregando informações sobre...</p>;
}

Quando um usuário navegar para `/about`, o componente `Loading` será exibido até que o componente `page.js` seja totalmente renderizado.

6. O Arquivo `error.js`

O arquivo `error.js` (ou `error.jsx`, `error.ts`, `error.tsx`) permite que você crie uma UI de erro personalizada que é exibida quando ocorre um erro dentro de um segmento de rota. Isso é útil para fornecer uma mensagem de erro mais amigável e evitar que a aplicação inteira trave.

Exemplo:

// app/about/error.js
'use client'

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Ocorreu um erro!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Tentar novamente</button>
    </div>
  );
}

Se ocorrer um erro ao renderizar a página `/about`, o componente `Error` será exibido. A prop `error` contém informações sobre o erro, e a função `reset` permite que o usuário tente recarregar a página.

7. Grupos de Rotas

Grupos de Rotas `(nomeDoGrupo)` permitem organizar suas rotas sem afetar a estrutura da URL. Eles são criados envolvendo o nome de uma pasta em parênteses. Isso é particularmente útil para organizar layouts e componentes compartilhados.

Exemplo:

app/
  (marketing)/
    about/
      page.js
    contact/
      page.js
  (shop)/
    products/
      page.js

Neste exemplo, as páginas `about` e `contact` são agrupadas sob o grupo `marketing`, e a página `products` está sob o grupo `shop`. As URLs permanecem `/about`, `/contact` e `/products`, respectivamente.

8. Rotas Dinâmicas

Rotas dinâmicas permitem criar rotas com segmentos variáveis. Isso é útil para exibir conteúdo com base em dados buscados de um banco de dados ou API. Segmentos de rota dinâmicos são definidos envolvendo o nome do segmento em colchetes (ex., `[id]`).

Exemplo:

Digamos que você queira criar uma rota para exibir postagens de blog individuais com base em seu ID. Você pode criar uma estrutura de arquivos como esta:

app/
  blog/
    [id]/
      page.js

O segmento `[id]` é um segmento dinâmico. O componente exportado de `app/blog/[id]/page.js` será renderizado quando um usuário navegar para uma URL como `/blog/123` ou `/blog/456`. O valor do parâmetro `id` estará disponível na prop `params` do componente.

// app/blog/[id]/page.js
import React from 'react';

export default async function BlogPost({ params }) {
  const { id } = params;

  // Busca dados para a postagem do blog com o ID fornecido
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>Postagem do blog não encontrada.</p>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

async function fetchBlogPost(id) {
  // Simula a busca de dados de um banco de dados ou API
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: 'Minha Primeira Postagem no Blog', content: 'Este é o conteúdo da minha primeira postagem no blog.' },
        '456': { title: 'Outra Postagem no Blog', content: 'Este é um conteúdo mais empolgante.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

Você também pode usar múltiplos segmentos dinâmicos em uma rota. Por exemplo, você poderia ter uma rota como `/blog/[category]/[id]`.

9. Segmentos Catch-all

Segmentos Catch-all (ou coringa) permitem criar rotas que correspondem a qualquer número de segmentos. Isso é útil para cenários como a criação de um CMS onde a estrutura da URL é determinada pelo usuário. Segmentos Catch-all são definidos adicionando três pontos antes do nome do segmento (ex., `[...slug]`).

Exemplo:

app/
  docs/
    [...slug]/
      page.js

O segmento `[...slug]` corresponderá a qualquer número de segmentos após `/docs`. Por exemplo, ele corresponderá a `/docs/getting-started`, `/docs/api/users` e `/docs/advanced/configuration`. O valor do parâmetro `slug` será um array contendo os segmentos correspondentes.

// app/docs/[...slug]/page.js
import React from 'react';

export default function DocsPage({ params }) {
  const { slug } = params;

  return (
    <div>
      <h1>Documentação</h1>
      <p>Slug: {slug ? slug.join('/') : 'Nenhum slug'}</p>
    </div>
  );
}

Segmentos catch-all opcionais podem ser criados adicionando o nome do segmento em colchetes duplos `[[...slug]]`. Isso torna o segmento de rota opcional. Exemplo:

app/
  blog/
    [[...slug]]/
      page.js

Esta configuração renderizará o componente page.js tanto em `/blog` quanto em `/blog/qualquer/numero/de/segmentos`.

10. Rotas Paralelas

Rotas Paralelas permitem renderizar simultaneamente uma ou mais páginas no mesmo layout. Isso é particularmente útil para layouts complexos, como painéis de controle (dashboards), onde diferentes seções da página podem ser carregadas independentemente. Rotas Paralelas são definidas usando o símbolo `@` seguido por um nome de slot (ex., `@sidebar`, `@main`).

Exemplo:

app/
  @sidebar/
    page.js  // Conteúdo para a barra lateral
  @main/
    page.js  // Conteúdo para a seção principal
  default.js // Obrigatório: Define o layout padrão para rotas paralelas

O arquivo `default.js` é obrigatório ao usar rotas paralelas. Ele define como os diferentes slots são combinados para criar o layout final.

// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
        {sidebar}
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>
        {main}
      </main>
    </div>
  );
}

11. Rotas de Interceptação

Rotas de Interceptação permitem carregar uma rota de uma parte diferente da sua aplicação dentro do layout atual. Isso é útil para criar modais, galerias de imagens e outros elementos de UI que devem aparecer sobre o conteúdo da página existente. Rotas de Interceptação são definidas usando a sintaxe `(..)` que indica quantos níveis acima na árvore de diretórios ir para encontrar a rota interceptada.

Exemplo:

app/
  (.)photos/
    [id]/
      page.js  // A rota interceptada
  feed/
    page.js  // A página onde o modal da foto é exibido

Neste exemplo, quando um usuário clica em uma foto na página `/feed`, a rota `app/(.)photos/[id]/page.js` é interceptada e exibida como um modal sobre a página `/feed`. A sintaxe `(.)` diz ao Next.js para procurar um nível acima (para o diretório `app`) para encontrar a rota `photos/[id]`.

Busca de Dados com o App Router

O App Router oferece suporte integrado para busca de dados usando Componentes de Servidor (Server Components) e Componentes de Cliente (Client Components). Os Componentes de Servidor são renderizados no servidor, enquanto os Componentes de Cliente são renderizados no cliente. Isso permite que você escolha a melhor abordagem para cada componente com base em seus requisitos.

Componentes de Servidor

Componentes de Servidor são o padrão no App Router. Eles permitem que você busque dados diretamente em seus componentes sem a necessidade de rotas de API separadas. Isso pode melhorar o desempenho e simplificar seu código.

Exemplo:

// app/products/page.js
import React from 'react';

export default async function ProductsPage() {
  const products = await fetchProducts();

  return (
    <div>
      <h1>Produtos</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

async function fetchProducts() {
  // Simula a busca de dados de um banco de dados ou API
  return new Promise((resolve) => {
    setTimeout(() => {
      const products = [
        { id: 1, name: 'Produto A' },
        { id: 2, name: 'Produto B' },
        { id: 3, name: 'Produto C' },
      ];
      resolve(products);
    }, 500);
  });
}

Neste exemplo, a função `fetchProducts` é chamada diretamente dentro do componente `ProductsPage`. O componente é renderizado no servidor, e os dados são buscados antes que o HTML seja enviado para o cliente.

Componentes de Cliente

Componentes de Cliente são renderizados no cliente e permitem que você use recursos do lado do cliente como ouvintes de eventos, estado e APIs do navegador. Para usar um Componente de Cliente, você precisa adicionar a diretiva `'use client'` no topo do arquivo.

Exemplo:

// app/counter/page.js
'use client'

import React, { useState } from 'react';

export default function CounterPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Contador</h1>
      <p>Contagem: {count}</p>
      <button onClick={() => setCount(count + 1)}>Incrementar</button>
    </div>
  );
}

Neste exemplo, o componente `CounterPage` é um Componente de Cliente porque usa o hook `useState`. A diretiva `'use client'` diz ao Next.js para renderizar este componente no cliente.

Técnicas Avançadas de Roteamento

O App Router oferece várias técnicas avançadas de roteamento que podem ser usadas para criar aplicações complexas e sofisticadas.

1. Manipuladores de Rota (Route Handlers)

Manipuladores de Rota permitem criar endpoints de API dentro do seu diretório `app`. Isso elimina a necessidade de um diretório `pages/api` separado. Manipuladores de Rota são definidos em arquivos chamados `route.js` (ou `route.ts`) e exportam funções que lidam com diferentes métodos HTTP (ex., `GET`, `POST`, `PUT`, `DELETE`).

Exemplo:

// app/api/users/route.js
import { NextResponse } from 'next/server'

export async function GET(request) {
  // Simula a busca de usuários de um banco de dados
  const users = [
    { id: 1, name: 'João Silva' },
    { id: 2, name: 'Maria Silva' },
  ];

  return NextResponse.json(users);
}

export async function POST(request) {
  const body = await request.json()
  console.log('Dados recebidos:', body)
  return NextResponse.json({ message: 'Usuário criado' }, { status: 201 })
}

Este exemplo define um manipulador de rota em `/api/users` que lida com requisições `GET` e `POST`. A função `GET` retorna uma lista de usuários, e a função `POST` cria um novo usuário.

2. Grupos de Rotas com Múltiplos Layouts

Você pode combinar grupos de rotas com layouts para criar diferentes layouts para diferentes seções da sua aplicação. Isso é útil para cenários onde você quer ter um cabeçalho ou barra lateral diferente para partes distintas do seu site.

Exemplo:

app/
  (marketing)/
    layout.js  // Layout de marketing
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // Layout de admin
    dashboard/
      page.js

Neste exemplo, as páginas `about` e `contact` usarão o layout de `marketing`, enquanto a página `dashboard` usará o layout de `admin`.

3. Middleware

Middleware permite executar código antes que uma requisição seja tratada pela sua aplicação. Isso é útil para tarefas como autenticação, autorização, logging e redirecionamento de usuários com base em sua localização ou dispositivo.

O Middleware é definido em um arquivo chamado `middleware.js` (ou `middleware.ts`) na raiz do seu projeto.

Exemplo:

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Verifica se o usuário está autenticado
  const isAuthenticated = false; // Substitua pela sua lógica de autenticação

  if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

// Veja "Matching Paths" abaixo para saber mais
export const config = {
  matcher: '/admin/:path*',
}

Este exemplo define um middleware que verifica se o usuário está autenticado antes de permitir o acesso a qualquer rota sob `/admin`. Se o usuário não estiver autenticado, ele é redirecionado para a página `/login`.

Melhores Práticas para Roteamento Baseado em Arquivos

Para aproveitar ao máximo o sistema de roteamento baseado em arquivos do App Router, considere as seguintes melhores práticas:

Exemplos de Internacionalização com o Next.js App Router

O App Router do Next.js simplifica a internacionalização (i18n) através do roteamento baseado em arquivos. Veja como você pode implementar i18n de forma eficaz:

1. Roteamento por Sub-caminho

Organize suas rotas com base no local (locale) usando sub-caminhos. Por exemplo:

app/
  [locale]/
    page.tsx         // Página inicial para o local
    about/
      page.tsx     // Página 'Sobre' para o local
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';

export default async function HomePage({ params: { locale } }) {
  const t = await getTranslations(locale);
  return (<h1>{t.home.title}</h1>);
}

// dictionaries.js
const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  pt: () => import('./dictionaries/pt.json').then((module) => module.default),
};

export const getTranslations = async (locale) => {
  try {
    return dictionaries[locale]() ?? dictionaries.en();
  } catch (error) {
    console.error(`Falha ao carregar traduções para o local ${locale}`, error);
    return dictionaries.en();
  }
};

Nesta configuração, o segmento de rota dinâmico `[locale]` lida com diferentes locais (ex., `/en`, `/pt`). As traduções são carregadas dinamicamente com base no local.

2. Roteamento por Domínio

Para uma abordagem mais avançada, você pode usar diferentes domínios ou subdomínios para cada local. Isso geralmente envolve configuração adicional com seu provedor de hospedagem.

3. Middleware para Detecção de Local

Use middleware para detectar automaticamente o local preferido do usuário e redirecioná-lo de acordo.

// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';

let locales = ['en', 'pt', 'es', 'fr'];

function getLocale(request) {
  const negotiatorHeaders = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();

  try {
      return match(languages, locales, 'pt-BR'); // Usa "pt-BR" como local padrão
  } catch (error) {
      console.error("Erro ao corresponder local:", error);
      return 'pt-BR'; // Retorna para português como fallback se a correspondência falhar
  }
}

export function middleware(request) {
  const pathname = request.nextUrl.pathname;
  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    return NextResponse.redirect(
      new URL(
        `/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
        request.url
      )
    );
  }
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Este middleware verifica se o caminho solicitado tem um prefixo de local. Se não, ele detecta o local preferido do usuário usando o cabeçalho `Accept-Language` e o redireciona para o caminho específico do local apropriado. Bibliotecas como `@formatjs/intl-localematcher` e `negotiator` são usadas para lidar com a negociação de local.

Next.js App Router e Acessibilidade Global

Criar aplicações web globalmente acessíveis requer uma consideração cuidadosa dos princípios de acessibilidade (a11y). O App Router do Next.js fornece uma base sólida para construir experiências acessíveis, mas é essencial implementar as melhores práticas para garantir que sua aplicação seja utilizável por todos, independentemente de suas habilidades.

Principais Considerações de Acessibilidade

  1. HTML Semântico: Use elementos HTML semânticos (ex., `<article>`, `<nav>`, `<aside>`, `<main>`) para estruturar seu conteúdo. Isso fornece significado às tecnologias assistivas e ajuda os usuários a navegar em seu site com mais facilidade.
  2. Atributos ARIA: Use atributos ARIA (Accessible Rich Internet Applications) para aprimorar a acessibilidade de componentes e widgets personalizados. Os atributos ARIA fornecem informações adicionais sobre o papel, estado e propriedades dos elementos para as tecnologias assistivas.
  3. Navegação por Teclado: Garanta que todos os elementos interativos sejam acessíveis via teclado. Os usuários devem ser capazes de navegar por sua aplicação usando a tecla `Tab` e interagir com os elementos usando as teclas `Enter` ou `Espaço`.
  4. Contraste de Cores: Use contraste de cores suficiente entre o texto e o fundo para garantir a legibilidade para usuários com deficiências visuais. As Diretrizes de Acessibilidade para Conteúdo da Web (WCAG) recomendam uma taxa de contraste de pelo menos 4.5:1 para texto normal e 3:1 para texto grande.
  5. Texto Alternativo para Imagens: Forneça texto alternativo descritivo para todas as imagens. O texto alternativo fornece uma alternativa textual para imagens que pode ser lida por leitores de tela.
  6. Rótulos de Formulário: Associe os rótulos de formulário aos seus campos de entrada correspondentes usando o elemento `<label>`. Isso deixa claro para os usuários quais informações são esperadas em cada campo.
  7. Teste com Leitores de Tela: Teste sua aplicação com um leitor de tela para garantir que ela seja acessível a usuários com deficiências visuais. Leitores de tela populares incluem NVDA, JAWS e VoiceOver.

Implementando Acessibilidade no Next.js App Router

  1. Use o Componente Link do Next.js: Use o componente `<Link>` para navegação. Ele fornece recursos de acessibilidade integrados, como prefetching e gerenciamento de foco.
  2. Gerenciamento de Foco: Ao navegar entre páginas ou abrir modais, garanta que o foco seja gerenciado adequadamente. O foco deve ser definido para o elemento mais lógico na nova página ou modal.
  3. Componentes Personalizados Acessíveis: Ao criar componentes personalizados, garanta que eles sejam acessíveis seguindo os princípios descritos acima. Use HTML semântico, atributos ARIA e navegação por teclado para tornar seus componentes utilizáveis por todos.
  4. Linting e Testes: Use ferramentas de linting como o ESLint com plugins de acessibilidade para identificar possíveis problemas de acessibilidade em seu código. Além disso, use ferramentas de teste automatizadas para testar sua aplicação em busca de violações de acessibilidade.

Conclusão

O sistema de roteamento baseado em arquivos do App Router do Next.js oferece uma maneira poderosa e intuitiva de estruturar e navegar em suas aplicações. Ao entender os conceitos centrais e as melhores práticas descritas neste guia, você pode construir aplicações Next.js robustas, escaláveis e de fácil manutenção. Experimente os diferentes recursos do App Router e descubra como ele pode simplificar seu fluxo de trabalho de desenvolvimento e melhorar a experiência do usuário.