Rutas protegidas con React Router y estado global
Hasta ahora hemos creado múltiples páginas con React Router y ya contamos con un estado global para manejar la sesión del usuario. Sin embargo, hay un problema importante: cualquier usuario puede acceder a páginas que deberían estar restringidas, como el perfil, incluso sin haber iniciado sesión.
En esta clase vamos a solucionar esto creando rutas protegidas, combinando React Router con el estado global para controlar el acceso de forma clara y reutilizable.
El problema de las rutas sin protección
Imagina una página /profile que muestra información del usuario.
Si el usuario no ha iniciado sesión:
- No debería poder acceder a esa página.
- No tiene sentido mostrar un perfil vacío o inexistente.
- Deberíamos redirigirlo automáticamente a
/login.
Sin protección, React Router renderiza la página igualmente si el usuario accede directamente a la URL.
La idea de una ruta protegida
Una ruta protegida es un componente que:
- Comprueba si el usuario tiene sesión iniciada.
- Si no la tiene, redirige a otra ruta (por ejemplo
/login). - Si sí la tiene, renderiza el contenido protegido.
Para esto vamos a crear un componente intermedio que actúe como guardián de la ruta.
Creando el componente ProtectedRoute
Creamos un componente pequeño y reutilizable, por ejemplo ProtectedRoute.jsx.
Este componente:
- Lee el estado global para saber si el usuario está autenticado.
- Usa
Navigatede React Router para redirigir si no hay sesión. - Renderiza
childrensi todo está correcto.
Conceptualmente hace esto:
¿Hay sesión?
Sí → renderiza la página
No → redirige a /login
Un ejemplo típico:
import { Navigate } from 'react-router-dom'
import { useAuthStore } from '../store/auth'
export function ProtectedRoute({ children }) {
const isLoggedIn = useAuthStore((state) => state.isLoggedIn)
if (!isLoggedIn) {
return <Navigate to="/login" replace />
}
return children
}
Integración con React Router
React Router no permite envolver rutas directamente como si fueran componentes normales.
Por eso, ProtectedRoute debe usarse dentro de la propiedad element de la ruta.
En lugar de renderizar directamente la página:
- Envolvemos la página dentro de
ProtectedRoute. - React Router evalúa primero la protección.
- Solo renderiza la página si el usuario cumple las condiciones.
Un ejemplo:
import { Route, Routes } from 'react-router-dom'
import { ProtectedRoute } from './ProtectedRoute'
import { ProfilePage } from './pages/ProfilePage'
import { LoginPage } from './pages/LoginPage'
export function AppRoutes() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
</Routes>
)
}
Esto puede parecer un poco rígido, pero es la forma correcta de hacerlo con React Router.
Protegiendo la página de perfil
Aplicamos ProtectedRoute a la ruta /profile.
Ahora ocurre lo siguiente:
Usuario sin sesión
- Intenta acceder a
/profile. - Es redirigido automáticamente a
/login.
Usuario con sesión
- Accede a
/profile. - Ve su información correctamente.
Además, si el usuario cierra sesión estando en /profile, el sistema lo redirige de nuevo a login automáticamente (porque el estado global cambia y la protección se re-evalúa).
Login y register como rutas públicas
Las páginas de login y registro:
- No deben estar protegidas.
- Se renderizan libremente.
- Al iniciar sesión, actualizan el estado global.
- Tras el login, pueden redirigir a una ruta privada como
/profile.
En esta fase el login está mockeado, pero la estructura ya es válida para conectar con un backend real más adelante.
Qué hemos conseguido hasta ahora
Con esta implementación:
- Ya tenemos navegación entre páginas.
- Hemos aprendido a proteger rutas sensibles.
- El acceso depende del estado global del usuario.
- La aplicación empieza a comportarse como una app real.
Además, todo esto es escalable. Podemos proteger tantas rutas como queramos reutilizando el mismo componente.
Qué viene a continuación
Hasta ahora todo el estado está en frontend y simulado. En las próximas clases nos centraremos en:
- Crear el backend real.
- Autenticación con servidor.
- Persistencia en base de datos.
- APIs, SQL y validación real de usuarios.
Con esto cerramos la parte de Estado Global y React Router, dejando una base sólida para conectar todo con backend.