Rotas protegidas
Algumas partes de um app só deveriam ser visíveis para um usuário autenticado: um dashboard, uma página de conta, qualquer coisa que busque dados pessoais. React Router não tem um recurso dedicado de "rota protegida", e não precisa de um. Os componentes de roteamento e rotas aninhadas se combinam para formar o padrão: uma rota de layout que verifica se o usuário está autenticado e renderiza seus filhos ou um redirecionamento para a página de login.
Uma coisa importante deixar clara antes de qualquer código. Proteger rotas no cliente é um recurso de experiência do usuário. Mantém visitantes não autenticados longe de páginas que buscariam seus dados e exibiriam telas vazias e quebradas. Nunca é segurança, porque tudo em um bundle de cliente é inspecionável e contornável.
A proteção real acontece no servidor, que deve recusar entregar dados para requisições que não estejam autenticadas. Autenticação cobre como essa aplicação funciona; este capítulo é sobre a parte do cliente.
Uma rota de layout com autenticação obrigatória
Uma rota de layout é uma rota pai cujo elemento renderiza UI compartilhada mais um Outlet para seus filhos. Uma rota de layout sem caminho não adiciona nenhum segmento de URL; existe puramente para envolver. Isso a torna o lugar perfeito para uma verificação de login: envolva cada rota que você quer proteger em uma rota de layout sem caminho cujo único trabalho é decidir se o Outlet renderiza.
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="login" element={<Login />} />
<Route element={<AuthRequired />}>
<Route path="host" element={<Dashboard />} />
<Route path="host/vans" element={<HostVans />} />
<Route path="host/vans/:id" element={<HostVanDetail />} />
</Route>
</Route>Tudo aninhado dentro de <Route element={<AuthRequired />}> agora está atrás do protetor, incluindo rotas com parâmetros como host/vans/:id. Proteger uma nova página depois é uma linha: mova sua rota para dentro do wrapper.
O protetor em si tem dois caminhos. Se o usuário está autenticado, renderize o Outlet para que o filho correspondente apareça. Se não estiver, renderize algo que o envie para a página de login em vez disso. Não renderizar nada é o truque todo: se um componente protegido nunca renderiza, qualquer busca de dados dentro dele nunca começa.
import { Outlet, Navigate } from 'react-router-dom'
export default function AuthRequired() {
const authenticated = false // substituto para uma verificação de sessão real
if (!authenticated) {
return <Navigate to="/login" />
}
return <Outlet />
}O curso finge a verificação com um booleano, e depois com um valor em localStorage, para manter o foco no roteamento. Uma verificação real lê o estado da sessão, frequentemente entregue através de contexto; novamente, Autenticação cobre de onde esse estado realmente vem.
Nota sobre versão
Os exemplos aqui importam de react-router-dom, que funciona tanto em v6 quanto em v7; Roteamento cobre o que v7 mudou sobre os pacotes. O curso ensina v6 em toda esta seção.
O componente Navigate
Link renderiza um âncora e espera ser clicado. Navigate pula a espera: assim que renderiza, o roteador move o usuário para seu caminho to. Isso o torna a ferramenta de redirecionamento para lógica de renderização.
Seu componente decide, no meio da renderização, que o usuário não deveria estar aqui, e retorna <Navigate to="/login" /> em vez de qualquer UI. Há também um hook useNavigate que retorna uma função para redirecionar de manipuladores de eventos, que é como o próprio formulário de login envia o usuário adiante após um envio bem-sucedido.
Chegar em uma página de login vazia sem explicação é confuso, então o protetor deveria dizer por que o usuário acabou lá. Navigate recebe a mesma prop state que Link recebe, então um redirecionamento pode entregar ao destino uma razão.
if (!authenticated) {
return (
<Navigate
to="/login"
state={{ message: 'Você deve fazer login primeiro' }}
/>
)
}import { useLocation } from 'react-router-dom'
function Login() {
const location = useLocation()
return (
<>
{location.state?.message && <h3>{location.state.message}</h3>}
<h1>Entre na sua conta</h1>
{/* form... */}
</>
)
}O optional chaining importa, porque alguém que clica direto na página de login nunca passou pelo protetor e encontra location.state definido como null.
A pilha de histórico e replace
O padrão até agora tem um bug que você pode sentir. Visite uma página protegida enquanto desautenticado, seja redirecionado, faça login, depois pressione o botão Voltar do navegador. Em vez da página que você estava antes de tudo isso, você chega na página de login de novo, completa com sua mensagem "você deve fazer login primeiro".
O navegador mantém uma pilha de histórico: cada navegação empurra uma nova entrada, e Voltar retorna à anterior. O redirecionamento empurrou /login na pilha logo após a página protegida, e a própria navegação do formulário de login empurrou a página protegida por cima disso. Voltar o caminha direto para as sobras.
A correção é fazer redirecionamentos substituir a entrada atual em vez de empurrar uma nova. No componente Navigate é a prop replace; na função useNavigate é uma opção.
// em AuthRequired: o redirecionamento substitui a si mesmo pela página bloqueada
return <Navigate to="/login" state={{ message: 'Você deve fazer login primeiro' }} replace />
// em Login, após um envio bem-sucedido
navigate('/host', { replace: true })Com ambos no lugar, o desvio de login nunca sobrevive no histórico. Voltar da página protegida agora vai para onde o usuário estava antes, e a página de login não pode ser revisitada por acidente. Uma boa regra de ouro: qualquer navegação que o usuário não pediu, que é o que um redirecionamento é, deve substituir.
De volta para onde estavam indo
Uma aspereza permanece. O formulário de login codifica navigate('/host', ...), então um usuário que estava tentando alcançar /host/vans/2 é deixado em /host depois de fazer login. O protetor conhece a URL bloqueada, porque useLocation dentro de AuthRequired descreve a página que o usuário estava tentando renderizar. Passe adiante no mesmo estado de navegação.
export default function AuthRequired() {
const location = useLocation()
const authenticated = false
if (!authenticated) {
return (
<Navigate
to="/login"
state={{
message: 'Você deve fazer login primeiro',
from: location.pathname + location.search + location.hash,
}}
replace
/>
)
}
return <Outlet />
}pathname sozinho não é o endereço completo: um usuário bloqueado em /host/vans?type=luxury estava indo para a lista filtrada, e descartar a query string remove o filtro. Adicionar search e hash reproduz cada parte da URL que o roteador rastreia. A página de login lê o valor de volta, com um fallback para pessoas que vieram para a página de login diretamente e portanto não têm estado.
function Login() {
const location = useLocation()
const navigate = useNavigate()
const from = location.state?.from || '/host'
function handleLogin() {
// ...ao sucesso:
navigate(from, { replace: true })
}
// ...
}Agora um link compartilhado para qualquer página protegida sobrevive ao desvio de login: bloqueado, redirecionado, autenticado, e entregue à URL exata que queriam.
Outlet e as páginas mostram normalmente. Se não estiver, ele renderiza Navigate, que o leva para a página de login no momento em que aparece. Adicionar replace mantém o redirecionamento fora da memória do botão Voltar, e passar junto para onde estavam indo deixa a página de login enviá-los direto de volta para lá.
Lembre-se que isso é gentileza para o usuário; o servidor ainda tem que proteger os dados em si.
A seguir: Como React renderiza, o modelo mental por trás de cada decisão de desempenho que se segue.

