El módulo 5 terminó con un diagnóstico incómodo: CicloUrbano funciona, pero es una sola pantalla. La Cabecera que escribiste en el módulo 2 lleva desde entonces un menú «Catálogo · Estaciones · Mis reservas» con tres enlaces que no llevan a ninguna parte —son <a href="#catalogo"> decorativos—, y esa deuda no se podía saldar porque faltaba la pieza que relaciona la URL del navegador con qué componente se pinta. Esa pieza no viene en React: hay que añadirla. En esta lección entenderás qué es el enrutamiento en el cliente, cómo funciona por dentro una aplicación de una sola página, qué gana CicloUrbano cuando cada pantalla tiene su propia dirección, y cuál de las tres formas de usar React Router v7 vas a emplear en el resto del curso. No escribirás todavía la configuración —eso es la próxima lección—; aquí construyes el modelo mental y el vocabulario que necesitarás para no perderte en él.
Contenido
- Qué problema resuelve un enrutador
- Por qué React no trae enrutamiento
- Cómo funciona una aplicación de una sola página
- La API de historial del navegador:
pushStateypopstate - Qué gana la aplicación con URLs reales
- Qué es React Router y su lugar en el ecosistema
- Las tres formas de usar React Router v7
- Vocabulario del módulo
- Historial de navegador frente a historial de hash
- El mapa de rutas de CicloUrbano
- Qué problema resuelve un enrutador
Empecemos por el estado actual del proyecto. Si quisieras hoy mostrar tres secciones distintas en CicloUrbano sin enrutador, harías algo así:
// src/App.jsx — enrutamiento "artesanal", lo que NO vas a mantener
import { useState } from 'react';
import Diseno from './componentes/Diseno.jsx';
import PanelCatalogo from './componentes/PanelCatalogo.jsx';
import PanelEstaciones from './componentes/PanelEstaciones.jsx';
import PanelReservas from './componentes/PanelReservas.jsx';
function App() {
const [seccion, setSeccion] = useState('catalogo');
return (
<Diseno alCambiarSeccion={setSeccion}>
{seccion === 'catalogo' && <PanelCatalogo />}
{seccion === 'estaciones' && <PanelEstaciones />}
{seccion === 'reservas' && <PanelReservas />}
</Diseno>
);
}
export default App;Funciona. Y sin embargo es una mala solución, por razones que no tienen que ver con el estilo del código:
- La URL nunca cambia. Un usuario que está viendo la ficha de
bici-002sigue teniendohttps://ciclourbano.test/en la barra de direcciones. No puede enviar ese enlace a nadie. - El botón «atrás» sale de la aplicación. El navegador no tiene ni idea de que el usuario ha «navegado» tres veces; para él no ha pasado nada desde que cargó la página. Pulsar atrás lo devuelve a Google.
- Recargar (F5) devuelve al principio. El
useStatese reinicia y vuelve al catálogo, dando igual dónde estuviera. - No hay marcadores. Guardar una pantalla en favoritos guarda siempre la portada.
- No hay historial interno. No puedes ir «atrás» dentro de la propia aplicación.
- Y escala fatal. Con nueve pantallas y parámetros (
¿qué bicicleta? ¿qué estación? ¿qué pestaña?), eseuseStatese convierte en un objeto con cinco campos y una cadena de condicionales imposible de leer.
Un enrutador es la pieza que mantiene sincronizadas dos cosas: la URL que ve el usuario y el árbol de componentes que React pinta. En un sentido, al cambiar la URL cambia lo que se ve; en el otro, al navegar dentro de la aplicación cambia la URL.
Esa doble dirección es la clave. Un enrutador no es «un switch más elegante»: es la decisión de convertir la URL en estado de la aplicación, un estado que vive fuera de React, que el navegador ya sabe gestionar y que el usuario puede editar, copiar y compartir.
- Por qué React no trae enrutamiento
Es una pregunta legítima: Angular trae su enrutador de serie, Vue tiene uno oficial. ¿Por qué React no?
La respuesta está en lo que React dice de sí mismo desde la lección 01-01: es una biblioteca para construir interfaces de usuario, no un marco de trabajo de aplicaciones. Su ámbito termina en «dado un estado, produce un árbol de elementos y sincronízalo con el DOM». Todo lo demás —enrutamiento, peticiones HTTP, gestión de estado global, internacionalización— queda deliberadamente fuera.
Esa decisión tiene consecuencias prácticas:
| Consecuencia | Detalle |
|---|---|
| React funciona fuera del navegador | React Native (10-05) no tiene URLs. Un enrutador basado en window.history no tendría sentido allí. |
| Compite el ecosistema | React Router, TanStack Router y otros han evolucionado en paralelo; las mejores ideas de uno acaban en el otro. |
| Cada proyecto elige | Una aplicación embebida en un panel de administración puede no necesitar URLs; una tienda las necesita para el SEO. |
| El coste es tuyo | Instalar, elegir versión, aprender una API que no está en la documentación de React. |
La contrapartida es real: hay que elegir, y la elección se equivoca a menudo porque el código antiguo que encuentras por internet usa APIs de hace tres versiones. Por eso el apartado 7 de esta lección es tan importante.
- Cómo funciona una aplicación de una sola página
Compara los dos modelos de navegación. En una web tradicional (multipágina), cada clic en un enlace es una petición completa al servidor:
sequenceDiagram
participant U as Usuario
participant N as Navegador
participant S as Servidor
U->>N: Clic en «Estaciones»
N->>N: Descarta la página actual (pantalla en blanco)
N->>S: GET /estaciones
S-->>N: estaciones.html
N->>S: GET estilos.css, scripts.js, imágenes…
S-->>N: recursos
N->>N: Analiza el HTML y pinta la página desde cero
N-->>U: Pantalla nueva (se perdió todo el estado de JavaScript)
En una aplicación de una sola página (SPA, single-page application) el servidor entrega un único index.html la primera vez, y a partir de ahí JavaScript se encarga de todo:
sequenceDiagram
participant U as Usuario
participant R as React Router
participant React
participant H as history (navegador)
U->>R: Clic en un <Link to="/estaciones">
R->>R: preventDefault() — no deja que el navegador navegue
R->>H: history.pushState(null, '', '/estaciones')
Note over H: La barra de direcciones cambia,<br/>NO hay petición al servidor
R->>R: Compara '/estaciones' con el mapa de rutas
R->>React: Renderiza <PaginaEstaciones />
React->>React: Reconciliación (01-05): sustituye solo lo que cambia
React-->>U: Pantalla nueva, sin recargar, estado intacto
Las diferencias que importan:
| Aspecto | Multipágina tradicional | Aplicación de una sola página |
|---|---|---|
| Peticiones por navegación | HTML + todos los recursos | Ninguna (o solo datos JSON) |
| Parpadeo entre pantallas | Sí, pantalla en blanco | No |
| Estado de JavaScript | Se pierde en cada navegación | Se conserva |
| Primera carga | Rápida (HTML ya montado) | Más lenta (hay que descargar el paquete JS) |
| Quién decide qué se ve | El servidor, por la ruta pedida | El enrutador, en el cliente |
| SEO por defecto | Bueno | Requiere cuidado (o SSR, 10-01) |
Fíjate en el detalle que hace posible todo el truco: preventDefault(). Un <a href="/estaciones"> normal provoca que el navegador tire la aplicación entera y pida la página al servidor. Un <Link to="/estaciones"> de React Router renderiza un <a> de verdad —con su href, para que se pueda abrir en pestaña nueva y para que los buscadores lo sigan— pero intercepta el clic y lo convierte en un cambio de estado interno. Esta distinción volverá en 06-02 y es la causa del error más común del módulo.
- La API de historial del navegador:
pushState y popstate
pushState y popstateReact Router no inventa nada mágico: se apoya en una API estándar del navegador que existe desde HTML5. Merece la pena verla en crudo una vez, porque entenderla despeja casi todas las dudas posteriores.
// Ejemplo didáctico en JavaScript puro (no forma parte de CicloUrbano)
// 1. Cambiar la URL SIN recargar y añadiendo una entrada al historial
history.pushState({ pantalla: 'estaciones' }, '', '/estaciones');
// La barra de direcciones ahora dice /estaciones.
// El servidor NO se ha enterado de nada. El DOM tampoco ha cambiado:
// pintar la pantalla nueva es responsabilidad TUYA.
// 2. Cambiar la URL SIN añadir entrada (sustituye la actual)
history.replaceState({ pantalla: 'acceso' }, '', '/acceso');
// Útil tras un inicio de sesión: no quieres que «atrás» vuelva al formulario.
// 3. Enterarte de que el usuario ha pulsado atrás o adelante
window.addEventListener('popstate', (evento) => {
console.log('El usuario navegó a:', location.pathname);
console.log('Estado asociado:', evento.state); // { pantalla: 'estaciones' }
pintarPantalla(location.pathname); // otra vez, cosa tuya
});Tres observaciones que conviene grabar:
pushStatecambia la URL pero no pinta nada. El navegador no dispara ninguna recarga ni ningún evento. Quien decide qué mostrar es tu código. Ese «tu código» es exactamente lo que React Router te ahorra escribir.popstatesolo se dispara con atrás/adelante, no conpushState. Por eso un enrutador escrito a mano necesita interceptar los clics además de escucharpopstate.- El estado que pasas a
pushStatese serializa y sobrevive a recargas dentro de la misma sesión. React Router lo aprovecha para la opciónstatede la navegación, que verás en 06-04.
Si tuvieras que escribir tu propio enrutador, necesitarías: interceptar clics, llamar a pushState, escuchar popstate, comparar la ruta con un conjunto de patrones, extraer los parámetros de los segmentos dinámicos, ordenar las coincidencias por especificidad, gestionar rutas anidadas, restaurar el desplazamiento… Es exactamente el trabajo que React Router lleva hecho desde 2014, y la razón de no hacerlo a mano.
- Qué gana la aplicación con URLs reales
Vale la pena enumerar las ganancias concretas, porque justifican la complejidad añadida:
- Enlaces compartibles. Un operario puede pasarle a otro
https://ciclourbano.test/bicicletas/bici-003y ambos ven la misma ficha. Sin URLs, la única instrucción posible es «entra y busca la Carga Max». - Atrás y adelante funcionan. El botón atrás es, con diferencia, el control más usado de cualquier navegador. Que se salga de la aplicación es un fallo grave de experiencia de usuario.
- Recargar mantiene el sitio. F5 en
/estaciones/est-02vuelve a/estaciones/est-02. Sin URLs, vuelve a la portada. - Marcadores. El usuario puede guardar «Mis reservas» en favoritos.
- Indexación por buscadores. Cada pantalla es un documento distinto con su dirección, y eso es condición necesaria (aunque no suficiente: hace falta que el contenido esté en el HTML, y ahí entra el SSR de 10-01) para que un buscador la muestre.
- Analítica útil. Las herramientas de medición cuentan páginas vistas por URL. Con una sola URL, todos tus datos dicen «portada».
- Estado depurable. Cuando un compañero te dice «me falla», la URL es la mitad del informe de error.
- División de código por pantalla. Si
/talleres una ruta propia, su JavaScript puede descargarse solo cuando alguien entra ahí. Esto es la carga perezosa que verás en 08-04, y sin rutas no hay dónde cortar.
- Qué es React Router y su lugar en el ecosistema
React Router es la biblioteca de enrutamiento para React más veterana y más usada. Traduce la URL actual a un árbol de componentes según un mapa de rutas que tú declaras, y ofrece los componentes y hooks para navegar, leer parámetros y componer pantallas anidadas.
Su historia explica gran parte de la confusión que encontrarás:
| Versión | Año aproximado | Novedad principal |
|---|---|---|
| v3 y anteriores | 2015–2016 | Rutas como configuración estática, browserHistory global |
| v4/v5 | 2017–2019 | «Todo son componentes»: <Switch>, coincidencia por orden |
| v6 | 2021 | <Routes>, mejor coincidencia (ya no importa el orden), rutas relativas, hooks |
| v6.4 | 2022 | createBrowserRouter, loader, action: el modo «de datos» |
| v7 | 2024–2025 | Unificación con Remix; tres modos oficiales; paquete react-router |
Y no está solo. Conviene saber qué hay alrededor, aunque en este curso uses React Router:
- TanStack Router. Alternativa moderna cuyo argumento fuerte es la seguridad de tipos: rutas, parámetros y parámetros de consulta tipados de extremo a extremo con TypeScript (10-04), con validación de los parámetros incluida. Si trabajas en un proyecto TypeScript grande y estricto, merece una mirada.
- Enrutadores integrados en los marcos de trabajo. Next.js (10-01 y 10-02) trae su enrutador basado en el sistema de ficheros: la ruta
/estaciones/est-02sale de la existencia de un ficheroapp/estaciones/[estacionId]/page.jsx, sin que declares un mapa. Lo mismo hace Remix (hoy fundido con React Router v7) y Astro. Cuando adoptas uno de esos marcos, no instalas React Router: usas el suyo. - Enrutadores minimalistas como
wouter, de unos pocos kilobytes, para aplicaciones pequeñas donde React Router resulta excesivo.
La conclusión práctica: React Router es la opción por defecto sensata para una SPA construida con Vite, que es exactamente el caso de CicloUrbano, y los conceptos que aprendas aquí (segmentos dinámicos, anidamiento, outlet, rutas índice) son los mismos en todos los enrutadores del ecosistema, con otros nombres.
- Las tres formas de usar React Router v7
Aquí está la principal fuente de desconcierto para quien empieza hoy. React Router v7 se puede usar de tres maneras distintas, y la documentación las llama modos:
| Modo | Cómo se define el mapa | Qué habilita | Cuándo elegirlo |
|---|---|---|---|
| Declarativo | <BrowserRouter> + <Routes> + <Route> en el JSX |
Enlaces, parámetros, anidamiento. Sin loader/action |
Migraciones desde v6; aplicaciones pequeñas; añadir rutas a una aplicación ya existente |
| De datos | createBrowserRouter([...]) como objeto + <RouterProvider> |
Todo lo anterior más loader, action, errorElement, useNavigation, useBlocker, ScrollRestoration |
SPA con Vite que quiere las capacidades modernas. Es el de este curso |
| De framework | Fichero routes.ts + complemento de Vite de React Router |
Todo lo del modo de datos más renderizado en servidor, generación de tipos, división de código automática | Cuando React Router hace de marco de trabajo completo, como alternativa a Next.js |
En este curso usamos el modo de datos: createBrowserRouter + RouterProvider. Las razones:
- Es el que recomienda la documentación oficial para una SPA nueva, y el camino de migración natural hacia el modo de framework si algún día hace falta.
- Desbloquea APIs que necesitamos.
errorElementpara manejar errores por rama de ruta (06-03),useBlockerpara impedir salir de un formulario a medias yScrollRestoration(06-04) solo existen en el modo de datos. Con el modo declarativo te quedarías sin ellas. - El mapa de rutas es un dato, no marcado. Un array de objetos se puede recorrer, transformar y usar para generar el menú o las migas de pan automáticamente. En 06-03 lo aprovechas con
handleyuseMatches. - Separa la configuración de la interfaz. El mapa vive en
src/rutas.jsxy no se mezcla con la pantalla.
Muy importante para leer código ajeno: la forma antigua sigue siendo válida y es la que verás en la inmensa mayoría del código existente, tutoriales y respuestas de foros:
// Modo DECLARATIVO (herencia de v6). Válido en v7, pero NO es lo que usaremos.
import { BrowserRouter, Routes, Route } from 'react-router';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<PaginaCatalogo />} />
<Route path="/estaciones" element={<PaginaEstaciones />} />
<Route path="*" element={<PaginaNoEncontrada />} />
</Routes>
</BrowserRouter>
);
}Y este es el equivalente en el modo que sí usarás, para que veas desde ya el parecido:
// Modo DE DATOS. Este es el estilo del curso (lo montarás entero en 06-02).
import { createBrowserRouter } from 'react-router';
export const router = createBrowserRouter([
{ path: '/', element: <PaginaCatalogo /> },
{ path: '/estaciones', element: <PaginaEstaciones /> },
{ path: '*', element: <PaginaNoEncontrada /> }
]);Los conceptos se trasladan uno a uno. path, element, el anidamiento, <Link>, useParams, useNavigate, <Outlet />: son idénticos en ambos modos. Lo único que cambia es dónde declaras el mapa y cómo se monta en la raíz. Si mañana caes en un proyecto v6, sabrás leerlo sin esfuerzo. La regla es la que ya aplicaste con CSS Modules en 02-05: elige una forma y sé coherente en todo el proyecto; mezclar <BrowserRouter> con <RouterProvider> en la misma aplicación es un error garantizado.
Una nota más sobre nombres de paquete, porque desconcierta a todo el mundo: en v6 se instalaba react-router-dom; en v7 el paquete es react-router y de él sale todo. react-router-dom sigue publicándose como reexportación por compatibilidad, así que verás ambos importes en la red. Lo detallamos en 06-02, donde instalarás.
- Vocabulario del módulo
Fija estos términos ahora; el resto del módulo los usa sin volver a definirlos.
| Término | Significado | Ejemplo en CicloUrbano |
|---|---|---|
| Ruta (route) | Regla que asocia un patrón de URL con un componente | { path: '/estaciones', element: <PaginaEstaciones /> } |
| Segmento | Cada trozo entre barras de la ruta | En /estaciones/est-02 hay dos: estaciones y est-02 |
| Segmento dinámico | Segmento variable, escrito con dos puntos, cuyo valor se captura | /bicicletas/:bicicletaId captura bici-003 |
| Parámetro de ruta (param) | El valor capturado por un segmento dinámico | { bicicletaId: 'bici-003' } vía useParams() |
| Ruta anidada | Ruta hija de otra, que se pinta dentro de su padre | incidencias dentro de /estaciones/:estacionId |
outlet |
El hueco donde el padre pinta a su hijo activo | <Outlet /> dentro de Diseno |
| Ruta índice | Hija que se pinta cuando la URL coincide exactamente con el padre | La pestaña flota en /estaciones/est-02 |
| Ruta comodín (splat) | *, coincide con cualquier cosa no capturada antes |
La página de no encontrado |
| Enlace | <Link> o <NavLink>: navegación sin recargar |
Los tres del menú de Cabecera |
| Navegación programática | Navegar desde el código, no desde un clic en un enlace | Ir a /reservas tras confirmar una reserva (06-04) |
| Parámetros de consulta (query params) | Lo que va tras ?, para filtros y ordenaciones |
/?tipo=electrica |
location |
Objeto con la URL actual descompuesta | { pathname, search, hash, state, key } |
| Coincidencia (match) | Ruta (o cadena de rutas) que la URL activa ahora mismo | useMatches() en las migas de pan |
Un detalle de criterio que se olvida a menudo, y que gobierna una decisión de diseño en 06-02: un segmento dinámico identifica un recurso; un parámetro de consulta modifica una vista. La ficha de una bicicleta concreta es /bicicletas/bici-003 (identidad); un catálogo filtrado por tipo eléctrico es /?tipo=electrica (modificación). Si dudas, pregúntate si sin ese dato la pantalla sigue teniendo sentido: sin bicicletaId no hay ficha que enseñar (dinámico); sin tipo el catálogo se ve entero (consulta).
- Historial de navegador frente a historial de hash
React Router puede gestionar la URL de dos maneras, y la elección tiene una consecuencia en el servidor que sorprende al desplegar por primera vez.
Historial de navegador (createBrowserRouter) |
Historial de hash (createHashRouter) |
|
|---|---|---|
| URL de ejemplo | https://ciclourbano.test/estaciones/est-02 |
https://ciclourbano.test/#/estaciones/est-02 |
| API que usa | history.pushState |
El fragmento # de la URL |
| Aspecto | Limpio, indistinguible de una web tradicional | Con almohadilla, delator |
| SEO | Correcto | Malo: el fragmento no llega al servidor |
| Requisito de servidor | Sí: reescritura a index.html |
No: sirve con alojamiento estático puro |
El requisito del servidor es la clave. Imagina que el usuario está en /estaciones/est-02 y pulsa F5. Esta vez el navegador sí pide /estaciones/est-02 al servidor de verdad, porque una recarga no la controla JavaScript. Si el servidor tiene un fichero por ruta, no encontrará nada y devolverá un 404, aunque en la aplicación la ruta funcionase perfectamente al navegar con enlaces.
La solución es configurar el servidor para que cualquier ruta desconocida devuelva index.html y deje que el enrutador del cliente resuelva. Es una línea de configuración en cualquier alojamiento moderno (Netlify, Vercel, Nginx, Apache), y el servidor de desarrollo de Vite ya lo hace por defecto, razón por la que este problema no aparece hasta el día del despliegue. Lo trataremos con detalle en 11-05.
flowchart TD
F5["El usuario recarga /estaciones/est-02"] --> SRV{"¿El servidor tiene<br/>ese fichero?"}
SRV -->|"Sin configurar"| E404["404 Not Found 💥<br/>la aplicación ni arranca"]
SRV -->|"Con reescritura a index.html"| OK["Devuelve index.html<br/>→ React arranca<br/>→ el enrutador lee la URL<br/>→ pinta PaginaDetalleEstacion ✅"]
style E404 fill:#fecaca
style OK fill:#dcfce7
Cuándo usar createHashRouter, entonces:
- Publicas en un alojamiento estático que no permite configurar reescrituras (el caso clásico: GitHub Pages sin trucos).
- Entregas la aplicación como fichero suelto, un CD-ROM interno o un
file://. - La incrustas en un sistema heredado cuyo servidor no controlas.
En cualquier otro caso, createBrowserRouter, que es el que usa CicloUrbano. La API es idéntica salvo el nombre de la función, así que cambiar de uno a otro es cuestión de una línea.
- El mapa de rutas de CicloUrbano
Este es el destino del módulo. Lo implementarás en 06-02 y 06-03, y lo respetarás durante el resto del curso:
| Ruta | Pantalla | Notas |
|---|---|---|
/ |
Catálogo de bicicletas | Acepta ?tipo= para el filtro del SelectorTipo |
/bicicletas/:bicicletaId |
Ficha de una bicicleta | Segmento dinámico: bici-001 … bici-005 |
/estaciones |
Listado de estaciones | Las tres TarjetaEstacion |
/estaciones/:estacionId |
Detalle de estación | Con pestañas anidadas |
/estaciones/:estacionId (índice) |
Pestaña flota | Ruta índice: se ve por defecto |
/estaciones/:estacionId/incidencias |
Pestaña incidencias | Ruta anidada hermana |
/reservas |
Mis reservas | Lee del ProveedorReservas |
/reservas/nueva |
Formulario de reserva | Redirige a /reservas al confirmar (06-04) |
/acceso |
Inicio de sesión ficticio | Elige entre usr-01 y usr-02 |
/taller |
Panel del operario | Protegida: solo rol operario (06-05) |
* |
No encontrado | Cualquier URL que no coincida |
Y así queda el árbol, ya con el anidamiento que construirás en 06-03:
flowchart TD
RAIZ["/ · Diseno<br/>(Cabecera + Outlet + PieDePagina)"]
RAIZ --> IDX["index · PaginaCatalogo"]
RAIZ --> BICI["bicicletas/:bicicletaId<br/>PaginaFichaBicicleta"]
RAIZ --> EST["estaciones<br/>PaginaEstaciones"]
RAIZ --> DET["estaciones/:estacionId<br/>PaginaDetalleEstacion"]
DET --> FLO["index · PestanaFlota"]
DET --> INC["incidencias · PestanaIncidencias"]
RAIZ --> RES["reservas<br/>PaginaReservas"]
RAIZ --> NUE["reservas/nueva<br/>PaginaNuevaReserva"]
RAIZ --> ACC["acceso<br/>PaginaAcceso"]
RAIZ --> PROT["(sin path) RutaProtegida 🔒"]
PROT --> TAL["taller · PaginaTaller"]
RAIZ --> NF["* · PaginaNoEncontrada"]
style PROT fill:#fde68a
style TAL fill:#fde68a
Observa tres decisiones que se justificarán en su momento:
Disenoes la ruta raíz, no un componente que envuelve aApp. AsíCabecerayPieDePaginano se desmontan ni se vuelven a montar al cambiar de pantalla (06-03).- El catálogo es una ruta índice, no
path: '/'repetido. Es la pantalla que se ve cuando la URL coincide con el padre y nada más. - La rama protegida es una ruta sin
path: agrupa pantallas bajo un guardián sin añadir ningún segmento a la URL./tallersigue siendo/taller, no/protegido/taller(06-05).
Errores Comunes y Consejos
Creer que React Router hace peticiones al servidor. No las hace. Cambiar de ruta con <Link> es una operación puramente de cliente: se actualiza la URL y React repinta. Los datos siguen viniendo de donde vinieran (tu dominio.js, un fetch, un loader). Si al pasar de / a /estaciones esperas que el servidor te mande HTML nuevo, tienes el modelo mental equivocado.
Confundir el modo declarativo con el de datos y mezclar sus APIs. Es el error número uno hoy. Copias un ejemplo con <BrowserRouter>, otro con createBrowserRouter, y acabas con useNavigate lanzando «useNavigate() may be used only in the context of a
Buscar en la documentación de React lo que está en la de React Router. No hay una sola mención a <Link> en react.dev, y no es un olvido: son proyectos distintos, con versiones y calendarios distintos. Ten a mano las dos referencias.
Instalar react-router-dom en v7 por costumbre. Funciona por compatibilidad, pero el paquete correcto es react-router. Si mezclas los dos importes en el mismo proyecto puedes acabar con dos copias del enrutador y errores de contexto incomprensibles.
Meter en la URL cosas que no deben estar ahí. La URL es pública, se comparte y se guarda en el historial. Un filtro de catálogo o el número de página sí van ahí; un token de sesión, un correo electrónico o el contenido de un formulario a medias, no.
Consejo: piensa el mapa de rutas antes de escribirlo. Dibujar en papel las pantallas y sus direcciones, como la tabla del apartado 10, ahorra reestructuraciones caras. La URL es una interfaz pública de tu aplicación: si mañana cambias /bicis/:id por /bicicletas/:bicicletaId, rompes los enlaces que la gente ya guardó.
Consejo: usa nombres de parámetro descriptivos. :bicicletaId y :estacionId en lugar de :id y :id. Cuando en 06-03 leas los parámetros de una ruta anidada tendrás varios a la vez, y dos id distintos son un problema; además el código se lee solo.
Ejercicios
Ejercicio 1: diagnosticar el enrutamiento artesanal
Repasa el componente App del apartado 1, el que usa useState para elegir sección. Un compañero propone «mejorarlo» leyendo el hash de la URL al arrancar:
Responde razonadamente:
- ¿Qué problema de la lista del apartado 1 resuelve este cambio?
- ¿Cuáles siguen sin resolverse?
- ¿Qué le falta como mínimo para que el botón «atrás» funcione?
Ejercicio 2: clasificar el mapa de rutas
Para cada una de estas necesidades de CicloUrbano, decide si corresponde a un segmento dinámico, un parámetro de consulta, una ruta anidada o una ruta comodín, y escribe la URL resultante:
- Ver la ficha de la bicicleta
bici-005. - Ver solo las bicicletas de tipo
cargaen el catálogo. - Ver las incidencias de la estación
est-02, dentro de su pantalla de detalle. - Mostrar «Página no encontrada» cuando alguien escribe
/estacionez. - Ordenar el listado de estaciones por número de plazas, de mayor a menor.
Ejercicio 3: elegir modo y tipo de historial
Para cada escenario, di qué modo de React Router v7 (declarativo, de datos, de framework) y qué creador de enrutador (createBrowserRouter o createHashRouter) usarías, y por qué:
- CicloUrbano tal como lo vas a construir: SPA con Vite, desplegada en Netlify.
- Un panel interno que se distribuye como una carpeta de ficheros que los operarios abren desde el disco de red, con doble clic sobre
index.html. - Una aplicación existente en React Router v6 con 40 rutas, a la que hay que añadir dos pantallas nuevas la semana que viene.
- Una web pública nueva de CicloUrbano, con catálogo indexable por buscadores y renderizado en servidor.
Soluciones
Solución 1
- Resuelve la recarga y, en parte, los marcadores: si el usuario está en
#estacionesy pulsa F5, la aplicación arranca en la sección correcta, porque el hash sí se conserva y el estado inicial se calcula a partir de él. También podría guardar la URL en favoritos. - Siguen sin resolverse: el botón atrás (nadie escucha
popstate, así que al retroceder cambia el hash pero React no se entera y la pantalla no cambia); los enlaces compartibles con parámetros (¿qué bicicleta?no cabe en un solo hash sin inventarse un formato); el crecimiento del condicional; y la indexación por buscadores, porque el hash no llega al servidor. - Como mínimo hacen falta dos cosas: un
useEffectque suscriba un manejador awindow.addEventListener('popstate', …)—con su limpieza correspondiente, como en 05-02— para sincronizar el estado cuando el usuario navega hacia atrás, y que todos los cambios de sección pasen porhistory.pushStateen vez de porsetSecciona secas. En cuanto escribas esas dos piezas habrás empezado a construir tu propio enrutador, que es justo lo que conviene no hacer.
Solución 2
| Necesidad | Tipo | URL |
|---|---|---|
1. Ficha de bici-005 |
Segmento dinámico | /bicicletas/bici-005 |
| 2. Catálogo filtrado por carga | Parámetro de consulta | /?tipo=carga |
3. Incidencias de est-02 |
Ruta anidada (con segmento dinámico en el padre) | /estaciones/est-02/incidencias |
| 4. URL inexistente | Ruta comodín | /estacionez → coincide con * |
| 5. Estaciones ordenadas por plazas | Parámetro de consulta | /estaciones?orden=plazas&sentido=desc |
El criterio en los casos 2 y 5 es el del apartado 8: el filtro y la ordenación modifican una vista que existe igual sin ellos, así que van en la consulta. En el caso 1, sin bici-005 no hay ficha posible: es identidad, y va en el camino.
Solución 3
- CicloUrbano en Netlify: modo de datos con
createBrowserRouter. Es una SPA nueva sin necesidades de servidor, quiere URLs limpias y va a usarerrorElement,useBlockeryScrollRestoration. Netlify permite configurar la reescritura aindex.htmlcon un fichero_redirectsde una línea. - Carpeta abierta desde el disco (
file://):createHashRouter, sin discusión. No hay servidor que pueda reescribir nada, y concreateBrowserRouterla primera recarga rompería la aplicación. El modo puede seguir siendo el de datos:createHashRoutertambién lo es. - Aplicación v6 con 40 rutas: mantener el modo declarativo que ya tiene y añadir las dos pantallas nuevas como dos
<Route>más. Reescribir 40 rutas para ganaruseBlockerno compensa; si el equipo decide migrar, que sea un proyecto propio y no un efecto colateral de una entrega. Coherencia por encima de modernidad. - Web pública con SEO y renderizado en servidor: el modo de framework de React Router v7 o directamente Next.js (10-01). En cuanto necesitas HTML generado en el servidor, el enrutamiento deja de ser solo cliente y el marco de trabajo debe encargarse de ambos lados. Aquí ni siquiera eliges entre historial de navegador y de hash: el hash queda descartado porque nunca llega al servidor.
Conclusión
El enrutamiento en el cliente resuelve el problema con el que se cerró el módulo 5: hacer que la URL y la interfaz se correspondan. Has visto que una aplicación de una sola página entrega un único index.html y que, a partir de ahí, la navegación consiste en interceptar los clics, llamar a history.pushState para cambiar la dirección sin recargar y dejar que el enrutador decida qué componente pintar, con la reconciliación de 01-05 encargándose de tocar solo lo que cambia. A cambio de esa complejidad, CicloUrbano gana enlaces compartibles, botones de atrás y adelante que funcionan, recarga en el punto correcto, marcadores, analítica por pantalla, indexación y la posibilidad de cargar el código de cada sección solo cuando hace falta.
También has fijado las decisiones que gobiernan el resto del módulo. React no trae enrutador porque es una biblioteca de interfaces, no un marco de trabajo, y el ecosistema ofrece React Router, TanStack Router y los enrutadores integrados de Next.js o Remix. De las tres formas de usar React Router v7 —declarativa, de datos y de framework— el curso emplea la de datos, con createBrowserRouter y RouterProvider, porque es la recomendada para una SPA con Vite y la única que habilita errorElement, useBlocker y ScrollRestoration; el clásico <BrowserRouter><Routes><Route> de v6 sigue siendo válido, es lo que verás en muchísimo código existente, y todos los conceptos se trasladan tal cual. Tienes el vocabulario del módulo —ruta, segmento dinámico, ruta anidada, outlet, ruta índice, enlace, navegación programática, parámetros de consulta—, sabes por qué createBrowserRouter exige que el servidor reescriba a index.html y en qué casos raros hace falta createHashRouter, y tienes delante el mapa completo de rutas de CicloUrbano.
Toca construirlo. En la próxima lección instalarás React Router, crearás src/rutas.jsx con el mapa, montarás <RouterProvider /> en main.jsx respetando el orden de proveedores que ya existe, reorganizarás el proyecto con una carpeta src/paginas/, sustituirás por fin los <a href="#…"> de la Cabecera por <NavLink> con clase activa, leerás el identificador de la bicicleta con useParams y pondrás el filtro del SelectorTipo en la URL con useSearchParams. La próxima lección es Configuración de React Router.
Curso de React
Módulo 1: Introducción a React
- ¿Qué es React?
- Configuración del Entorno de Desarrollo
- Hola Mundo en React
- JSX: Extensión de Sintaxis de JavaScript
- Cómo Renderiza React: Virtual DOM y Reconciliación
Módulo 2: Componentes de React
- Entendiendo los Componentes
- Componentes Funcionales vs de Clase
- Props: Pasando Datos a Componentes
- State: Gestión del Estado del Componente
- Estilos en los Componentes: CSS, Módulos y Utilidades
Módulo 3: Trabajando con Eventos
- Manejo de Eventos en React
- Renderizado Condicional
- Listas y Claves
- Formularios y Componentes Controlados
- Validación de Formularios y Componentes No Controlados
- Accesibilidad en Componentes Interactivos
Módulo 4: Conceptos Avanzados de Componentes
- Elevando el Estado
- Composición vs Herencia
- Métodos del Ciclo de Vida de React
- Hooks: Introducción y Uso Básico
- Límites de Error: Capturar Fallos en la Interfaz
Módulo 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef y Acceso al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalizados
Módulo 6: Enrutamiento en React
- Introducción a React Router
- Configuración de React Router
- Rutas Anidadas
- Navegación Programática
- Rutas Protegidas y Control de Acceso
Módulo 7: Gestión del Estado
- Introducción a la Gestión del Estado
- API de Contexto
- Redux: Introducción y Configuración
- Redux: Acciones y Reductores
- Redux: Conectando a React
- Estado del Servidor: Peticiones, Caché y Sincronización
Módulo 8: Optimización del Rendimiento
- Técnicas de Optimización del Rendimiento en React
- Memorización con React.memo
- Hooks useMemo y useCallback
- División de Código y Carga Perezosa
- Medir el Rendimiento con React DevTools Profiler
Módulo 9: Pruebas en React
- Introducción a las Pruebas
- Pruebas Unitarias con Jest
- Pruebas de Componentes con React Testing Library
- Pruebas de Código Asíncrono y Simulación de APIs
- Pruebas de Extremo a Extremo con Cypress
Módulo 10: Temas Avanzados
- Renderizado del Lado del Servidor (SSR) con Next.js
- Generación de Sitios Estáticos (SSG) con Next.js
- Suspense y React Server Components
- TypeScript con React
- React Native: Creación de Aplicaciones Móviles
