React Router: navegação em SPAs

1. Introdução ao React Router e SPAs

Single Page Applications (SPAs) são aplicações web que carregam uma única página HTML e atualizam dinamicamente o conteúdo sem recarregar a página inteira. Diferente da navegação tradicional, onde cada clique em um link faz uma requisição HTTP ao servidor e recarrega todo o documento, as SPAs manipulam o histórico do navegador via JavaScript para simular a navegação entre "páginas" virtuais.

O React Router é a biblioteca padrão para gerenciar essa navegação client-side em aplicações React. Ele sincroniza a interface com a URL do navegador, permitindo que você defina rotas, acesse parâmetros, proteja páginas e muito mais — tudo sem recarregar a página.

Para começar, instale o pacote principal:

npm install react-router-dom

A configuração básica envolve envolver sua aplicação com um roteador e definir as rotas:

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

2. Componentes Fundamentais do React Router

BrowserRouter vs HashRouter

  • BrowserRouter: Usa a API de histórico do HTML5 para manter a UI sincronizada com a URL. As URLs são limpas (ex: /about). Requer configuração no servidor para redirecionar todas as requisições para o index.html.
  • HashRouter: Usa o hash da URL (ex: /#/about). Funciona sem configuração de servidor, pois o hash nunca é enviado ao servidor. Útil para protótipos ou quando você não controla o servidor.

Routes e Route

O componente Routes agrupa todas as rotas. Cada Route define um caminho (path) e o componente a ser renderizado (element):

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/products" element={<Products />} />
  <Route path="/contact" element={<Contact />} />
</Routes>

Link substitui a tag <a> tradicional, prevenindo o recarregamento da página:

<Link to="/about">Sobre</Link>

NavLink é uma versão especial que adiciona automaticamente uma classe CSS quando a rota está ativa:

<NavLink to="/about" className={({ isActive }) => isActive ? 'active' : ''}>
  Sobre
</NavLink>

3. Navegação Programática e Parâmetros de Rota

useNavigate

Para redirecionar via JavaScript (após login, envio de formulário, etc.), use o hook useNavigate:

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    // lógica de autenticação
    await loginUser(data);
    navigate('/dashboard');
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

Parâmetros Dinâmicos com useParams

Defina parâmetros na rota usando : e acesse-os com useParams:

// Configuração da rota
<Route path="/user/:id" element={<UserProfile />} />

// Componente UserProfile
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { id } = useParams();
  return <h1>Perfil do usuário {id}</h1>;
}

Parâmetros de Consulta com useSearchParams

Para ler e modificar query strings, use useSearchParams:

import { useSearchParams } from 'react-router-dom';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category') || 'all';

  const filterByCategory = (cat) => {
    setSearchParams({ category: cat });
  };

  return (
    <div>
      <button onClick={() => filterByCategory('electronics')}>
        Eletrônicos
      </button>
      <p>Exibindo produtos da categoria: {category}</p>
    </div>
  );
}

4. Rotas Aninhadas e Layouts Compartilhados

O React Router permite criar estruturas de rotas hierárquicas usando o componente Outlet. Isso é ideal para layouts compartilhados (header, footer, sidebar).

// App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './Layout';
import Home from './Home';
import Products from './Products';
import ProductDetail from './ProductDetail';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="products" element={<Products />} />
          <Route path="products/:id" element={<ProductDetail />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
// Layout.js
import { Outlet, Link } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <header>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/products">Produtos</Link>
        </nav>
      </header>
      <main>
        <Outlet /> {/* Aqui o conteúdo da rota filha será renderizado */}
      </main>
      <footer>© 2025 Minha Loja</footer>
    </div>
  );
}

Rotas relativas em componentes aninhados usam caminhos sem barra inicial ("products" em vez de "/products"). Para caminhos absolutos, use a barra ("/products").

5. Proteção de Rotas e Autenticação

Para criar rotas privadas, implemente um componente ProtectedRoute que verifica o estado de autenticação:

import { Navigate, Outlet } from 'react-router-dom';

function ProtectedRoute({ isAuthenticated, redirectPath = '/login' }) {
  if (!isAuthenticated) {
    return <Navigate to={redirectPath} replace />;
  }
  return <Outlet />;
}

Uso no roteador:

<Routes>
  <Route path="/login" element={<Login />} />
  <Route element={<ProtectedRoute isAuthenticated={user !== null} />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/settings" element={<Settings />} />
  </Route>
</Routes>

Para persistência de sessão, armazene tokens no localStorage ou sessionStorage e verifique ao carregar a aplicação:

function App() {
  const [user, setUser] = useState(() => {
    const saved = localStorage.getItem('user');
    return saved ? JSON.parse(saved) : null;
  });

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<Login setUser={setUser} />} />
        <Route element={<ProtectedRoute isAuthenticated={!!user} />}>
          <Route path="/dashboard" element={<Dashboard />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

6. Tratamento de Erros e Rotas Coringa

Rota 404

Use path="*" para capturar qualquer URL não definida:

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="*" element={<NotFound />} />
</Routes>

Componente Navigate para Redirecionamentos Condicionais

Além do useNavigate, você pode usar o componente Navigate para redirecionamentos declarativos:

import { Navigate } from 'react-router-dom';

function OldPage() {
  return <Navigate to="/new-page" replace />;
}

Tratamento de Erros de Navegação

Crie um fallback para erros inesperados usando Error Boundaries combinados com o React Router:

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <h1>Algo deu errado. <Link to="/">Voltar ao início</Link></h1>;
    }
    return this.props.children;
  }
}

7. Otimização com Lazy Loading e Code Splitting

Para melhorar a performance em SPAs, carregue componentes de rota sob demanda usando React.lazy e Suspense:

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Products = lazy(() => import('./pages/Products'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Carregando...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/products" element={<Products />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Isso reduz o bundle inicial, pois cada página só é carregada quando o usuário a acessa. Combine com ferramentas como Webpack ou Vite para divisão automática de código.

Boas práticas adicionais:
- Use React.lazy apenas para componentes de página, não para componentes pequenos.
- Forneça um fallback visualmente agradável no Suspense.
- Considere pré-carregar rotas prováveis com React.lazy + preload para melhorar a experiência do usuário.


Referências