React: React Router: Tópicos avançados

Última atualização: 2026-08-26

Tom implementou com sucesso o roteamento básico, mas novos requisitos continuam surgindo: os usuários precisam ser redirecionados automaticamente para o painel após o login; certas páginas exigem login para acesso; à medida que o aplicativo cresce, a página inicial demora mais para carregar; e a página da lista de produtos precisa suportar parâmetros de filtro na URL... Ele precisa aprender os recursos avançados do React Router para lidar com esses cenários do mundo real.


1. O que você vai aprender



2. Diagramas conceituais

100%
flowchart TD
    U[User Actions] --> A{Are you logged in??}
    A -->|Not logged in| B[ProtectedRoute<br/>→ Redirect to /login]
    A -->|Logged in| C{Permissions?}
    C -->|No permission| D[→ 403 Page]
    C -->|Has permission| E[Load the target page]
    E --> F{Is the component lazily loaded??}
    F -->|is | G[Suspense<br/>Show loading]
    G --> H[Rendering Page]
    F -->|No| H
    style A fill:#fff3e0,stroke:#f57c00
    style B fill:#ffcdd2,stroke:#d32f2f
    style G fill:#e1f5fe,stroke:#0288d1
    style H fill:#e8f5e9,stroke:#388e3c

Solicitação do usuário → O Route Guard verifica o login e as permissões → A página é carregada após a aprovação (exibe “carregando” durante o carregamento diferido) → Renderização final.



3. Um cenário da vida real

O aplicativo do Tom exige o seguinte: após um login bem-sucedido, o usuário deve ser redirecionado automaticamente para o painel de controle; a página do painel de controle deve ser protegida por login; somente usuários com a função “admin” devem ter acesso ao painel de administração; a página inicial deve carregar rapidamente na primeira tela (com páginas maiores carregadas sob demanda); e as URLs das páginas de lista de produtos devem incluir parâmetros de filtragem e paginação para facilitar o compartilhamento.

(1) Navegação programática: useNavigate

Tom precisa processar os envios de formulários na página de login e, caso o envio seja bem-sucedido, redirecionar os usuários para páginas diferentes, de acordo com suas funções. Esse tipo de navegação, acionada pela lógica do código, não pode usar <Link> (já que os links só podem ser acionados por cliques dos usuários); em vez disso, deve-se usar useNavigate.

useNavigate Retorna uma função navigate que suporta três métodos de chamada:

Uso Efeito Exemplo
navigate('/path') Ir para um caminho especificado (adicionar uma nova entrada à pilha do histórico) navigate('/dashboard')
navigate('/path', { replace: true }) Substituir o histórico atual (não é possível voltar a este ponto) Redirecionar após o login
navigate(-1) Voltar Voltar à página anterior

▶ Exemplo 1: Navegação programática após o login

JSX
import { useNavigate, useLocation } from 'react-router-dom'

function LoginPage() {
  const navigate = useNavigate()
  const location = useLocation()

  // from  URL Retrieve the URL to redirect to after login from the query parameters
  const from = location.state?.from?.pathname || '/dashboard'

  function handleLogin(event) {
    event.preventDefault()
    const formData = new FormData(event.target)
    const username = formData.get('username')

    // Simulate a login request
    fakeLogin(username).then(user => {
      if (user.role === 'admin') {
        navigate('/admin', { replace: true })     // Replace Record,Cannot go back to the login page
      } else {
        navigate(from, { replace: true })          // Redirect to the page you were trying to access before logging in
      }
    })
  }

  // Login Form
  return (
    <form onSubmit={handleLogin}>
      <input name="username" placeholder="Username" required />
      <button type="submit">Log In</button>
      <button type="button" onClick={() => navigate(-1)}>Back</button>
    </form>
  )
}

// Simulated Login API
async function fakeLogin(username) {
  await new Promise(r => setTimeout(r, 500))
  return { name: username, role: username === 'admin' ? 'admin' : 'user' }
}
▶ Experimente

A função do useLocation: location.state Ele pode receber dados de estado passados pelo Link ou pelo navigate. Por exemplo, ao redirecionar para uma página de login dentro de um ProtectedRoute, é possível armazenar o caminho da página que o usuário estava tentando acessar originalmente em state, de modo que, após o login, o usuário seja redirecionado automaticamente de volta para essa página.

(2) Proteção de rota: ProtectedRoute

Tipo de proteção Lógica de verificação Comportamento em caso de falha Cenários típicos
Login Guard isAuthenticated Redirecionar para /login Painel, Minha conta
Permissions Guard user.role === 'admin' Redirecionar para /403 Painel de administração
Proteção de botões featureFlags.xEnabled Redirecionar para /upgrade Recursos pagos
Verificação condicional profileComplete Redirecionar para /onboarding Guia para o primeiro login

A página do painel do Tom exige que o usuário faça login para acessá-la, e o painel de administração requer a função “admin”. Ele precisa de um mecanismo de “proteção” — que verifique o status do usuário antes de exibir a página e o redirecione caso as condições não sejam atendidas.

Em essência, o RouteGuard é um componente wrapper: ele aceita componentes filhos e executa a lógica de validação antes da renderização. Se a validação for bem-sucedida, ele renderiza os componentes filhos; caso contrário, ele redireciona para outra página usando <Navigate>.

▶ Exemplo 2: Proteção de roteamento em várias camadas

JSX 📖 Somente leitura
import { Navigate, useLocation } from 'react-router-dom'

// Simulated Certification Hook
function useAuth() {
  return {
    user: { name: 'Tom', role: 'admin' },  // In actual projects, starting from Context or  store Get
    isAuthenticated: true
  }
}

// First Floor:Login Guard
function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth()
  const location = useLocation()

  if (!isAuthenticated) {
    // Save the user's destination to state in ,Redirect back after logging in
    return <Navigate to="/login" state={{ from: location }} replace />
  }

  return children
}

// Second Floor:Character Guard
function AdminRoute({ children }) {
  const { user } = useAuth()

  if (user.role !== 'admin') {
    return <Navigate to="/403" replace />
  }

  return children
}

// Used in router configuration
function AppRoutes() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<LoginPage />} />
      <Route path="/403" element={<AccessDenied />} />

      {/* You must log in */}
      <Route path="/dashboard" element={
        <ProtectedRoute>
          <Dashboard />
        </ProtectedRoute>
      } />

      {/* You must log in + admin Character */}
      <Route path="/admin" element={
        <ProtectedRoute>
          <AdminRoute>
            <AdminPanel />
          </AdminRoute>
        </ProtectedRoute>
      } />
    </Routes>
  )
}
45 linhas de lógica (limite de 40, somente leitura)

Padrão de Proteção Aninhada: O ProtectedRoute externo verifica “se o usuário está conectado”, enquanto o AdminRoute interno verifica “se o usuário possui permissões de administrador”. Isso separa as responsabilidades e permite a reutilização. Se a função “Editar” for necessária no futuro, basta adicionar um EditorRoute.

(3) Carregamento diferido: React.lazy + Suspense

À medida que o aplicativo crescia, Tom percebeu que a página inicial estava demorando cada vez mais para carregar — como todo o código de todas as páginas estava agrupado em um único pacote, os usuários precisavam baixar o código do painel de administração sempre que acessavam a página inicial. O React.lazy permite que o código dos componentes seja dividido em partes separadas, que são carregadas somente quando necessário.

JSX
import { lazy, Suspense } from 'react'

// These components will not be bundled into the main bundle in 
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
const AdminPanel = lazy(() => import('./pages/AdminPanel'))
const UserList = lazy(() => import('./pages/UserList'))
▶ Experimente

▶ Exemplo 3: Carregamento diferido no nível da rota

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

// Lazy-load all page components
const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const ProductList = lazy(() => import('./pages/ProductList'))
const ProductDetail = lazy(() => import('./pages/ProductDetail'))
const Settings = lazy(() => import('./pages/Settings'))
const NotFound = lazy(() => import('./pages/NotFound'))

// Loading component
function PageLoader() {
  return (
    <div style={{
      display: 'flex', justifyContent: 'center', alignItems: 'center',
      height: '100vh', fontSize: '1.2rem', color: '#666'
    }}>
      <div className="spinner" />
      <span style={{ marginLeft: '12px' }}>Page is loading...</span>
    </div>
  )
}

function App() {
  return (
    <BrowserRouter>
      {/* Suspense Wrap all lazy-loading routes */}
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/products" element={<ProductList />} />
          <Route path="/products/:id" element={<ProductDetail />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

export default App
▶ Experimente

Como funciona o carregamento diferido: O React carrega dinamicamente o bloco /dashboard apenas quando um usuário acessa /dashboard pela primeira vez. Durante o carregamento, é exibido o componente fallback do Suspense. Assim que o carregamento for concluído, ele é substituído pelo componente Dashboard propriamente dito.

Benefícios de desempenho: Supondo que o pacote original tenha 500 KB, o carregamento diferido reduz o pacote principal para 100 KB, com cada página pesando cerca de 80 KB. Quando os usuários acessam a página inicial, precisam baixar apenas 100 KB em vez de 500 KB — o que resulta em uma melhoria de aproximadamente 5 vezes na velocidade de carregamento da parte visível da página.



4. useSearchParams: Gerenciamento de parâmetros de consulta de URL

Método de parâmetro de URL API Exemplo de sintaxe Dados aplicáveis
Parâmetro de caminho useParams() /products/:id{ id: '42' } Identificador obrigatório (ID do recurso)
Parâmetros de consulta useSearchParams() ?sort=price&page=2 Filtragem/classificação/paginação opcionais
Hash useLocation().hash #section-3 Links de âncora da página
Estado useLocation().state navigate('/path', { state }) Transmissão de dados ocultos entre páginas

A página da lista de produtos do Tom precisa oferecer recursos como filtragem por categoria, ordenação por preço e paginação, e esses critérios de filtragem devem constar na URL — para que os usuários possam compartilhar os links filtrados com outras pessoas.

(1) Leitura e gravação de parâmetros de consulta

useSearchParams Retorna um objeto semelhante a um Map que permite ler e gravar parâmetros de consulta de URL da mesma forma que Map. Assim como useState, ele retorna um valor e um setter.

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

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams()

  const category = searchParams.get('category') || 'All'
  const sort = searchParams.get('sort') || 'default'
  const page = Number(searchParams.get('page')) || 1

  function updateFilter(key, value) {
    setSearchParams(prev => {
      if (value) prev.set(key, value)
      else prev.delete(key)
      return prev
    })
  }

  return (
    <div>
      <p>Current Category:{category} | Sort:{sort} | Page: {page}</p>
      <button onClick={() => updateFilter('category', 'Electronics')}>Electronics Categories</button>
      <button onClick={() => updateFilter('sort', 'price')}>Sort by Price</button>
      <button onClick={() => updateFilter('page', String(page + 1))}>Next Page</button>
      <button onClick={() => setSearchParams({})}>Clear Filters</button>
    </div>
  )
}
// URL It will be updated in real time:/products?category=Electronics&sort=price&page=2
▶ Experimente

▶ Exemplo 4: Funcionalidade completa de filtragem de produtos

JSX 📖 Somente leitura
import { useSearchParams } from 'react-router-dom'

// Simulated Product Data
const allProducts = [
  { id: 1, name: 'iPhone 16', category: 'Electronics', price: 6999 },
  { id: 2, name: 'React Programming Books', category: 'Books', price: 79 },
  { id: 3, name: 'Mechanical Keyboard', category: 'Electronics', price: 399 },
  { id: 4, name: 'Introduction to Design Patterns', category: 'Books', price: 59 },
  { id: 5, name: 'Bluetooth Headphones', category: 'Electronics', price: 899 },
]

function ProductListPage() {
  const [searchParams, setSearchParams] = useSearchParams()

  // from  URL Read Filter Criteria
  const category = searchParams.get('category') || ''
  const sortBy = searchParams.get('sort') || 'name'
  const page = parseInt(searchParams.get('page') || '1', 10)
  const pageSize = 3

  // Filtering
  let filtered = category
    ? allProducts.filter(p => p.category === category)
    : allProducts

  // Sort
  if (sortBy === 'price') {
    filtered = [...filtered].sort((a, b) => a.price - b.price)
  } else {
    filtered = [...filtered].sort((a, b) => a.name.localeCompare(b.name))
  }

  // Pagination
  const totalPages = Math.ceil(filtered.length / pageSize)
  const paged = filtered.slice((page - 1) * pageSize, page * pageSize)

  // Update Filter Criteria
  function setFilter(key, value) {
    setSearchParams(prev => {
      const next = new URLSearchParams(prev)
      if (value) {
        next.set(key, value)
      } else {
        next.delete(key)
      }
      next.set('page', '1')  // Return to the first page when switching filters
      return next
    })
  }

  return (
    <div>
      <div style={{ marginBottom: '16px' }}>
        <label>Categories:
          <select value={category} onChange={e => setFilter('category', e.target.value)}>
            <option value="">All</option>
            <option value="Electronics">Electronics</option>
            <option value="Books">Books</option>
          </select>
        </label>
        <label style={{ marginLeft: '16px' }}>Sort:
          <select value={sortBy} onChange={e => setFilter('sort', e.target.value)}>
            <option value="name">Name</option>
            <option value="price">Price</option>
          </select>
        </label>
      </div>

      <ul>
        {paged.map(p => (
          <li key={p.id}>{p.name} — ${p.price}({p.category})</li>
        ))}
      </ul>

      <div>
        {Array.from({ length: totalPages }, (_, i) => (
          <button
            key={i}
            onClick={() => setSearchParams(prev => {
              const next = new URLSearchParams(prev)
              next.set('page', String(i + 1))
              return next
            })}
            style={{ fontWeight: page === i + 1 ? 'bold' : 'normal' }}
          >
            {i + 1}
          </button>
        ))}
      </div>

      <p>Currently URL:/products?category={category}&sort={sortBy}&page={page}</p>
    </div>
  )
}
77 linhas de lógica (limite de 40, somente leitura)

Palavra-chave: Os parâmetros de consulta da URL representam um “estado compartilhável”. Depois que um usuário aplica filtros e copia a URL para enviar a um colega, esse colega verá exatamente os mesmos resultados filtrados ao abrir o link. Isso não é possível ao usar useState para gerenciar condições de filtro.



5. Estratégias organizacionais para a configuração de roteamento

Quando o número de rotas do projeto chega a várias dezenas, ter todas as rotas escritas em um único componente torna o código difícil de manter. Tom precisa extrair a configuração das rotas para um módulo separado.

(1) Arquivo de configuração de roteamento

JSX
// src/routes/index.js
import { lazy } from 'react'

// Centrally manage all routing definitions
const routes = [
  {
    path: '/',
    component: lazy(() => import('../pages/Home')),
    exact: true
  },
  {
    path: '/login',
    component: lazy(() => import('../pages/Login')),
  },
  {
    path: '/dashboard',
    component: lazy(() => import('../pages/Dashboard')),
    protected: true  // You must log in
  },
  {
    path: '/admin',
    component: lazy(() => import('../pages/AdminPanel')),
    protected: true,
    adminOnly: true   // Required admin Permissions
  },
  {
    path: '/products',
    component: lazy(() => import('../pages/ProductList')),
  },
  {
    path: '/products/:id',
    component: lazy(() => import('../pages/ProductDetail')),
  },
  {
    path: '*',
    component: lazy(() => import('../pages/NotFound')),
  }
]

export default routes
▶ Experimente

▶ Exemplo 5: Renderizador de rota

JSX
// src/routes/AppRouter.jsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { Suspense } from 'react'
import routes from './index'
import ProtectedRoute from '../components/ProtectedRoute'
import AdminRoute from '../components/AdminRoute'

function renderRoutes(routeList) {
  return routeList.map(route => {
    const Component = route.component

    let element = <Component />

    // On-Demand Package Guard
    if (route.protected) {
      element = <ProtectedRoute>{element}</ProtectedRoute>
    }
    if (route.adminOnly) {
      element = <AdminRoute>{element}</AdminRoute>
    }

    return (
      <Route key={route.path} path={route.path} element={element} />
    )
  })
}

function AppRouter() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading......</div>}>
        <Routes>
          {renderRoutes(routes)}
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

export default AppRouter
▶ Experimente

A vantagem dessa abordagem é que as configurações de roteamento ficam centralizadas em um único local, a lógica de proteção é encapsulada automaticamente e, para adicionar uma nova página, basta adicionar um objeto ao routes/index.js.

(2) useLocation: monitorar alterações na rota

Além da navegação, o Tom também precisa monitorar mudanças na rota em determinados cenários — por exemplo, para enviar dados de rastreamento quando o caminho da página muda, fechar janelas pop-up ou redefinir o estado dos formulários. O useLocation pode recuperar as informações da URL atual e, quando usado em conjunto com o useEffect, monitorar mudanças no caminho.

JSX
import { useLocation } from 'react-router-dom'
import { useEffect } from 'react'

function PageTracker() {
  const location = useLocation()

  useEffect(() => {
    // Triggered whenever the path changes
    console.log('Page Views:', location.pathname + location.search)

    // Tracking Event Reporting
    analytics.pageView({
      path: location.pathname,
      search: location.search,
      timestamp: Date.now()
    })
  }, [location])  // Dependency location Object
  // Note:location.pathname or  location.search Any change will trigger

  return null  // This component does not render anything UI
}

// in  App Used in
function App() {
  return (
    <BrowserRouter>
      <PageTracker />  {/* Place Routes External,Always Listen */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </BrowserRouter>
  )
}
▶ Experimente

Principais propriedades retornadas por useLocation: pathname (caminho, como /products/42), search (string de consulta, como ?category=electronics), hash (hash da URL, como #section-2), state (dados de estado passados por meio de Link ou navigate).


❓ Perguntas Frequentes

P: Como faço para escolher entre useNavigate e Link? R: Link é usado para navegação acionada por cliques do usuário (como links na barra de navegação, trilha de navegação ou listas de artigos). useNavigate é usado para navegação acionada por lógica de código (como redirecionamentos após o login, após o envio de formulários, redirecionamentos programados ou navegação para trás/para frente). Regra básica: interação do usuário → Link; lógica de código → useNavigate.

P: Os guardas de rota podem ser aninhados em vários níveis? Há alguma precaução que deva ser levada em conta? R: Sim. Um padrão comum é que o guarda externo ProtectedRoute verifique “se o usuário está conectado”, enquanto o guarda interno AdminRoute verifica “se o usuário possui permissões de administrador”. Ao aninhar guardas, certifique-se de que cada guarda tenha uma única responsabilidade; não verifique tanto o status de login quanto as permissões de função dentro de um único guarda. Se houver guardas demais, considere usar um array de configuração combinado com um loop para envolver automaticamente várias camadas de guardas.

P: Quais são as limitações do carregamento diferido do React.lazy? R: Existem três limitações: (1) Ele só pode ser usado com componentes exportados por meio da exportação padrão (export default); (2) Deve ser usado dentro de um componente Suspense; (3) Não oferece suporte à renderização no lado do servidor (para SSR, use @loadable/component em vez disso). Além disso, se a conexão de rede estiver lenta enquanto um componente carregado de forma preguiçosa estiver sendo carregado, os usuários verão o conteúdo de fallback; recomenda-se que o fallback seja projetado para ser pequeno e rápido.

P: Qual é a diferença entre usar useSearchParams e useState para gerenciar condições de filtro? R: useSearchParams sincroniza o estado com a URL — os usuários podem compartilhar links, adicionar páginas aos favoritos e navegar para frente e para trás. O estado gerenciado por useState existe apenas na memória e é perdido quando a página é atualizada. No entanto, useSearchParams acarreta uma sobrecarga de desempenho (mudanças na URL acionam a re-renderização do componente). Para cenários com atualizações frequentes (como arrastar um controle deslizante), recomenda-se usar useState primeiro e, em seguida, sincronizar o estado com a URL assim que ele for confirmado.

P: Qual é a diferença entre route guards e middleware? R: Route guards são interceptadores no nível do componente React — eles envolvem o componente alvo e, após verificarem as condições, decidem se devem renderizar o componente ou redirecionar. Middleware são interceptadores no nível do servidor do Next.js — eles executam lógica (como autenticação, redirecionamento e testes A/B) depois que uma solicitação chega ao servidor, mas antes que a página seja renderizada. Os guardas do React Router são executados no lado do cliente, enquanto o middleware do Next.js é executado no lado do servidor, no Edge Runtime.


📖 Resumo


📝 Exercícios

  1. Implementar uma página de login: Depois que o usuário digitar seu nome de usuário, use useNavigate para navegar até o painel de controle e use replace: true durante a navegação para impedir que o usuário volte à página de login.
  2. Use ProtectedRoute para proteger as rotas do painel, redirecionando para a página de login quando o usuário não estiver conectado e redirecionando automaticamente para a página que o usuário pretendia visitar originalmente após um login bem-sucedido.
  3. Use useSearchParams para implementar uma página de lista de produtos: ela deve oferecer filtragem por categoria, ordenação por preço e paginação. Os critérios de filtragem devem constar na URL e devem ser mantidos após a atualização da página.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%