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.LinkeNavLink: 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...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
indexpara a rota padrão do pai. - Links absolutos desnecessários: em páginas de módulo, use
Linkrelativo quando fizer sentido (comoto={id}), para manter o módulo independente do prefixo.
Checklist prático para aplicar no seu projeto
- Crie um componente
AppRoutescom 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.