Capa do Ebook gratuito Roteamento e Autenticação em React: Construindo SPAs Seguras com React Router e JWT

Roteamento e Autenticação em React: Construindo SPAs Seguras com React Router e JWT

Novo curso

20 páginas

Configuração de rotas com React Router e organização por módulos

Capítulo 3

Tempo estimado de leitura: 10 minutos

+ Exercício
Audio Icon

Ouça em áudio

0:00 / 0:00

Neste capítulo, o foco é configurar rotas com React Router e organizar o roteamento por módulos (feature-based), de forma que a aplicação cresça sem virar um arquivo único de rotas difícil de manter. A ideia central é separar responsabilidades: cada módulo (por exemplo, auth, dashboard, users) declara suas próprias rotas, e um ponto de entrada agrega tudo em um roteador principal.

O que significa “configurar rotas” em uma SPA

Em uma SPA (Single Page Application), a navegação acontece sem recarregar o documento inteiro. O React Router observa a URL e decide qual componente renderizar. Em vez de o servidor entregar uma página diferente para cada caminho, o servidor entrega a mesma aplicação, e o roteamento é resolvido no cliente.

Na prática, “configurar rotas” envolve: definir caminhos (paths), escolher quais componentes renderizar em cada caminho, criar layouts (componentes que envolvem páginas), lidar com rotas aninhadas, parâmetros (ex.: /users/:id), rotas de fallback (404), e, quando necessário, proteger rotas (o tema de proteção e JWT aparece em outros capítulos; aqui vamos preparar a estrutura para isso sem repetir a teoria).

Visão geral do React Router (v6+)

O React Router moderno (v6+) incentiva uma configuração declarativa com <Routes> e <Route>, além de suportar rotas aninhadas com <Outlet />. Os elementos mais usados na configuração são:

  • BrowserRouter: usa a History API do navegador (URLs “limpas”).
  • Routes: container que escolhe a rota que casa com a URL.
  • Route: define um caminho e o elemento a renderizar.
  • Outlet: ponto onde rotas filhas são renderizadas dentro de um layout.
  • Navigate: redirecionamento declarativo.
  • useParams: lê parâmetros de rota.
  • useNavigate: navegação programática.
  • Link e NavLink: navegação por links sem reload.

Você pode configurar rotas de duas formas principais: via JSX (<Routes>) ou via “router object” (createBrowserRouter) com RouterProvider. Para organização por módulos, ambas funcionam; aqui vamos priorizar a abordagem JSX por ser direta e comum, e depois mostrar como modularizar mantendo legibilidade.

Continue em nosso aplicativo

Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.

ou continue lendo abaixo...
Download App

Baixar o aplicativo

Passo a passo: configuração mínima do roteador

1) Envolver a aplicação com BrowserRouter

O primeiro passo é garantir que o React Router esteja no topo da árvore de componentes, normalmente no ponto de entrada da aplicação.

import React from "react");

Exemplo típico (ajuste os nomes conforme seu projeto):

import React from "react");

Um exemplo completo e correto:

import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";

createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

O BrowserRouter precisa envolver qualquer componente que use Routes, Link, hooks de navegação etc.

2) Criar um componente de rotas principal

Em vez de misturar rotas com o restante do App, é comum criar um componente dedicado, por exemplo AppRoutes. Isso facilita a organização e testes.

import { Routes, Route, Navigate } from "react-router-dom";
import HomePage from "./pages/HomePage";
import NotFoundPage from "./pages/NotFoundPage";

export function AppRoutes() {
  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/home" element={<Navigate to="/" replace />} />
      <Route path="*" element={<NotFoundPage />} />
    </Routes>
  );
}

O path="*" captura qualquer rota não definida e é a forma mais simples de criar um 404.

3) Renderizar AppRoutes no App

import { AppRoutes } from "./routes/AppRoutes";

export default function App() {
  return <AppRoutes />;
}

Até aqui, você já tem roteamento funcionando. O próximo passo é organizar por módulos e introduzir layouts e rotas aninhadas.

Layouts e rotas aninhadas: base para organização por módulos

Rotas aninhadas permitem criar um layout compartilhado para um conjunto de páginas. Por exemplo: um layout público (com header simples) e um layout privado (com sidebar). O layout renderiza um <Outlet />, que é onde a rota filha aparece.

Exemplo de layout público

import { Outlet, Link } from "react-router-dom";

export function PublicLayout() {
  return (
    <div>
      <header>
        <nav>
          <Link to="/">Início</Link> | {" "}
          <Link to="/auth/login">Entrar</Link>
        </nav>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

Exemplo de layout “app” (área logada)

import { Outlet, NavLink } from "react-router-dom";

export function AppLayout() {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "240px 1fr" }}>
      <aside>
        <nav>
          <ul>
            <li><NavLink to="/app" end>Dashboard</NavLink></li>
            <li><NavLink to="/app/users">Usuários</NavLink></li>
            <li><NavLink to="/app/settings">Configurações</NavLink></li>
          </ul>
        </nav>
      </aside>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

O uso de NavLink ajuda a estilizar o item ativo automaticamente, o que é útil em menus.

Organização por módulos: como dividir as rotas

Organizar por módulos significa que cada “feature” controla suas páginas e suas rotas, e o roteador principal apenas compõe esses módulos. Isso reduz acoplamento e torna mais fácil adicionar/remover funcionalidades.

Uma forma prática é cada módulo exportar um conjunto de <Route> (como um componente) ou exportar uma lista de objetos de rota para ser agregada. A abordagem com JSX é simples: cada módulo exporta um componente “Routes” do módulo.

Estratégia A: cada módulo exporta um componente com suas rotas

Exemplo: módulo de autenticação exporta AuthRoutes. Módulo de usuários exporta UsersRoutes. O roteador principal importa e encaixa.

Exemplo de rotas do módulo auth:

import { Routes, Route, Navigate } from "react-router-dom";
import LoginPage from "../pages/LoginPage";
import RegisterPage from "../pages/RegisterPage";

export function AuthRoutes() {
  return (
    <Routes>
      <Route path="/auth/login" element={<LoginPage />} />
      <Route path="/auth/register" element={<RegisterPage />} />
      <Route path="/auth" element={<Navigate to="/auth/login" replace />} />
    </Routes>
  );
}

Essa estratégia funciona, mas tem uma limitação: você não pode ter múltiplos <Routes> concorrendo no mesmo nível para a mesma URL sem cuidado, e fica mais difícil criar layouts aninhados globais. Por isso, em projetos maiores, é mais comum exportar “fragmentos” de <Route> para serem agregados em um único <Routes> principal.

Estratégia B (recomendada): cada módulo exporta fragmentos de Route

Aqui, cada módulo exporta um componente que retorna um <>...</> com várias <Route>, sem criar um <Routes> próprio. Assim, existe um único <Routes> no app, e os módulos apenas contribuem com rotas.

Exemplo: módulo auth exportando rotas como fragmento:

import { Route, Navigate } from "react-router-dom";
import LoginPage from "../pages/LoginPage";
import RegisterPage from "../pages/RegisterPage";

export function AuthRouteEntries() {
  return (
    <>
      <Route path="/auth" element={<Navigate to="/auth/login" replace />} />
      <Route path="/auth/login" element={<LoginPage />} />
      <Route path="/auth/register" element={<RegisterPage />} />
    </>
  );
}

Exemplo: módulo users com rotas aninhadas (lista e detalhe):

import { Route } from "react-router-dom";
import UsersListPage from "../pages/UsersListPage";
import UserDetailsPage from "../pages/UserDetailsPage";

export function UsersRouteEntries() {
  return (
    <>
      <Route path="/app/users" element={<UsersListPage />} />
      <Route path="/app/users/:id" element={<UserDetailsPage />} />
    </>
  );
}

Agora o roteador principal agrega tudo:

import { Routes, Route, Navigate } from "react-router-dom";
import { PublicLayout } from "../layouts/PublicLayout";
import { AppLayout } from "../layouts/AppLayout";
import LandingPage from "../pages/LandingPage";
import NotFoundPage from "../pages/NotFoundPage";

import { AuthRouteEntries } from "../modules/auth/routes/AuthRouteEntries";
import { UsersRouteEntries } from "../modules/users/routes/UsersRouteEntries";

export function AppRoutes() {
  return (
    <Routes>
      <Route element={<PublicLayout />}>
        <Route path="/" element={<LandingPage />} />
        <AuthRouteEntries />
      </Route>

      <Route path="/app" element={<AppLayout />}>
        <Route index element={<Navigate to="/app" replace />} />
        <Route path="" element={<div>Dashboard</div>} />
        <UsersRouteEntries />
      </Route>

      <Route path="*" element={<NotFoundPage />} />
    </Routes>
  );
}

Observação importante: quando você usa <Route path="/app" element={...}> com filhos, os filhos podem ser declarados com paths relativos (ex.: users) em vez de absolutos (ex.: /app/users). Isso costuma deixar o módulo mais reutilizável. A seguir, vamos ajustar para esse padrão.

Rotas relativas para módulos mais reutilizáveis

Quando um módulo declara rotas com paths absolutos, ele fica “preso” a um prefixo. Se você quiser mover o módulo de /app/users para /admin/users, terá que editar várias strings. Com rotas relativas, você monta o módulo em um “ponto” e ele se adapta.

Exemplo: declarar o módulo de usuários com paths relativos:

import { Route } from "react-router-dom";
import UsersListPage from "../pages/UsersListPage";
import UserDetailsPage from "../pages/UserDetailsPage";

export function UsersRouteEntries() {
  return (
    <>
      <Route path="users" element={<UsersListPage />} />
      <Route path="users/:id" element={<UserDetailsPage />} />
    </>
  );
}

E no roteador principal, você “monta” esse módulo dentro de /app:

<Route path="/app" element={<AppLayout />}>
  <Route index element={<div>Dashboard</div>} />
  <UsersRouteEntries />
</Route>

Agora, se amanhã você quiser que a área logada seja /admin, basta trocar o path do layout e o módulo continua funcionando.

Passo a passo: montando um módulo completo com rotas, páginas e navegação

Vamos montar um módulo “Users” com duas páginas (lista e detalhes) e navegação por link. O objetivo é mostrar como o roteamento por módulos se conecta ao dia a dia do desenvolvimento.

1) Criar as páginas do módulo

Página de lista:

import { Link } from "react-router-dom";

export default function UsersListPage() {
  const users = [
    { id: "1", name: "Ana" },
    { id: "2", name: "Bruno" },
  ];

  return (
    <section>
      <h2>Usuários</h2>
      <ul>
        {users.map((u) => (
          <li key={u.id}>
            <Link to={u.id}>{u.name}</Link>
          </li>
        ))}
      </ul>
    </section>
  );
}

Note o detalhe: to={u.id} é um link relativo. Se a rota atual é /app/users, o link vira /app/users/1. Isso combina muito bem com rotas relativas e módulos montáveis.

Página de detalhes:

import { useParams, useNavigate } from "react-router-dom";

export default function UserDetailsPage() {
  const { id } = useParams();
  const navigate = useNavigate();

  return (
    <section>
      <h2>Detalhes do usuário</h2>
      <p>ID: {id}</p>
      <button onClick={() => navigate(-1)}>Voltar</button>
    </section>
  );
}

2) Declarar as rotas do módulo

import { Route } from "react-router-dom";
import UsersListPage from "../pages/UsersListPage";
import UserDetailsPage from "../pages/UserDetailsPage";

export function UsersRouteEntries() {
  return (
    <>
      <Route path="users" element={<UsersListPage />} />
      <Route path="users/:id" element={<UserDetailsPage />} />
    </>
  );
}

3) Montar o módulo no layout correto

Se “Users” é parte da área logada, ele deve ficar dentro do layout /app:

<Route path="/app" element={<AppLayout />}>
  <Route index element={<div>Dashboard</div>} />
  <UsersRouteEntries />
</Route>

Index routes, rotas padrão e redirecionamentos

Um padrão comum é ter uma rota “pai” com layout e uma rota index (padrão) que aparece quando o path do pai é acessado sem subcaminho. Isso evita criar paths vazios confusos.

Exemplo: dentro de /app, a rota index pode ser o dashboard:

<Route path="/app" element={<AppLayout />}>
  <Route index element={<DashboardPage />} />
  <UsersRouteEntries />
</Route>

Para redirecionar um caminho “antigo” para um novo, use Navigate com replace para não poluir o histórico:

<Route path="/painel" element={<Navigate to="/app" replace />} />

Rotas de erro (404) e fallback por seção

Além do 404 global (path="*"), você pode criar um fallback dentro de uma seção. Por exemplo, qualquer rota desconhecida dentro de /app pode renderizar um “não encontrado” específico da área logada.

<Route path="/app" element={<AppLayout />}>
  <Route index element={<DashboardPage />} />
  <UsersRouteEntries />
  <Route path="*" element={<div>Página não encontrada na área logada</div>} />
</Route>

Isso melhora a experiência do usuário e ajuda a depurar links quebrados em áreas específicas.

Boas práticas de organização do arquivo de rotas

  • Um único <Routes> no topo: facilita entender a árvore de navegação e evita conflitos.
  • Módulos exportam entradas de rota: cada módulo controla suas rotas, mas não cria seu próprio roteador.
  • Preferir paths relativos dentro de módulos: aumenta reutilização e reduz manutenção.
  • Layouts por seção: agrupe rotas públicas e rotas da área logada sob layouts diferentes.
  • Evitar lógica complexa dentro da configuração: se precisar de regras (ex.: proteção), encapsule em componentes de guarda (guards) para manter as rotas legíveis.
  • Nomear rotas e caminhos de forma consistente: por exemplo, sempre plural para listas (users) e singular com parâmetro para detalhe (users/:id).

Carregamento sob demanda (lazy loading) por módulo

Quando a aplicação cresce, carregar todas as páginas de uma vez pode aumentar o bundle inicial. Uma forma simples de otimizar é usar React.lazy e Suspense para carregar páginas sob demanda. Isso combina muito bem com organização por módulos, porque cada módulo pode “lazy-load” suas páginas.

Exemplo: lazy loading de páginas no módulo de usuários:

import React from "react";
import { Route } from "react-router-dom";

const UsersListPage = React.lazy(() => import("../pages/UsersListPage"));
const UserDetailsPage = React.lazy(() => import("../pages/UserDetailsPage"));

export function UsersRouteEntries() {
  return (
    <>
      <Route path="users" element={<UsersListPage />} />
      <Route path="users/:id" element={<UserDetailsPage />} />
    </>
  );
}

Para isso funcionar, você precisa envolver a área onde essas rotas são renderizadas com um <Suspense>. Um lugar comum é no layout (assim o fallback vale para todas as páginas daquele layout):

import React from "react";
import { Outlet } from "react-router-dom";

export function AppLayout() {
  return (
    <div>
      <React.Suspense fallback={<div>Carregando...</div>}>
        <Outlet />
      </React.Suspense>
    </div>
  );
}

Assim, ao entrar em /app/users, o bundle do módulo é carregado quando necessário.

Erros comuns ao modularizar rotas (e como evitar)

  • Declarar paths absolutos dentro de módulos e depois mover o módulo: prefira rotas relativas e monte o módulo sob um pai.
  • Esquecer o <Outlet /> no layout: sem ele, as rotas filhas nunca aparecem.
  • Usar <Routes> em múltiplos lugares sem intenção: isso fragmenta a visão do roteamento e pode gerar comportamento inesperado. Centralize o <Routes> no roteador principal.
  • Confundir index route com path vazio: use index para a rota padrão do pai.
  • Links absolutos desnecessários: em páginas de módulo, use Link relativo quando fizer sentido (como to={id}), para manter o módulo independente do prefixo.

Checklist prático para aplicar no seu projeto

  • Crie um componente AppRoutes com um único <Routes>.
  • Defina layouts por seção (público e área logada) e use <Outlet />.
  • Para cada módulo, crie um arquivo de rotas que exporta fragmentos de <Route>.
  • Use paths relativos dentro do módulo sempre que o módulo for montado sob um pai.
  • Inclua 404 global e, se útil, 404 por seção.
  • Considere lazy loading para páginas de módulos com menor frequência de acesso.

Agora responda o exercício sobre o conteúdo:

Ao organizar o roteamento por módulos em React Router, qual prática torna um módulo mais reutilizável caso o prefixo da área mude de /app para /admin?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Usar paths relativos dentro do módulo permite montá-lo sob diferentes rotas pai (como /app ou /admin) sem alterar várias strings. O layout do pai usa Outlet para renderizar as rotas filhas.

Próximo capitúlo

Layouts e composição de páginas para área pública e área autenticada

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.