En esta página

React Router v7

15 min lectura TextoCap. 3 — Patrones y navegación

React Router v7 — el estándar para SPA

React Router es la librería de enrutamiento más usada en el ecosistema React. La versión 7 introdujo el concepto de rutas como unidades con loaders y actions, acercándolo al modelo de Remix.

Instalación

npm install react-router-dom

createBrowserRouter — la API moderna

La API recomendada desde React Router v6.4 es createBrowserRouter con la configuración de rutas como array de objetos:

import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      { index: true, element: <Inicio /> },
      { path: 'blog', element: <Blog /> },
      { path: 'blog/:slug', element: <Articulo /> },
    ],
  },
]);

function main() {
  ReactDOM.createRoot(document.getElementById('root')!).render(
    <RouterProvider router={router} />
  );
}
import { Link, NavLink } from 'react-router-dom';

function Navegacion() {
  return (
    <nav>
      {/* Link básico: navegación sin recarga */}
      <Link to="/">Inicio</Link>

      {/* NavLink: sabe si está activo */}
      <NavLink
        to="/cursos"
        className={({ isActive, isPending }) =>
          isActive ? 'nav-activo' : isPending ? 'nav-cargando' : ''
        }
      >
        Cursos
      </NavLink>

      {/* Link con estado (accesible vía useLocation) */}
      <Link to="/detalle/1" state={{ desde: 'lista' }}>
        Ver detalle
      </Link>
    </nav>
  );
}

Parámetros de ruta y navegación programática

import { useParams, useNavigate, useSearchParams, useLocation } from 'react-router-dom';

function DetalleProducto(): React.JSX.Element {
  // Parámetros de la URL: /productos/:categoria/:id
  const { categoria, id } = useParams<{ categoria: string; id: string }>();

  // Navegación programática
  const navegar = useNavigate();

  // Query params: /productos?pagina=2&orden=precio
  const [searchParams, setSearchParams] = useSearchParams();
  const pagina = Number(searchParams.get('pagina') ?? '1');

  // Estado de la ubicación actual
  const ubicacion = useLocation();

  const irPaginaSiguiente = () => {
    setSearchParams({ pagina: String(pagina + 1), orden: searchParams.get('orden') ?? 'fecha' });
  };

  return (
    <div>
      <p>Categoría: {categoria} | ID: {id}</p>
      <p>Página actual: {pagina}</p>
      <button type="button" onClick={() => navegar(-1)}>← Atrás</button>
      <button type="button" onClick={() => navegar('/carrito')}>Ir al carrito</button>
      <button type="button" onClick={irPaginaSiguiente}>Siguiente página</button>
      <p>Veniste desde: {String(ubicacion.state as { desde?: string } | null)?.toString()}</p>
    </div>
  );
}

Layouts anidados y Outlet

Outlet es el marcador donde React Router renderiza la ruta hija activa:

function LayoutDashboard(): React.JSX.Element {
  return (
    <div className="dashboard">
      <aside>
        <nav>
          <NavLink to="/dashboard">Resumen</NavLink>
          <NavLink to="/dashboard/configuracion">Configuración</NavLink>
          <NavLink to="/dashboard/facturacion">Facturación</NavLink>
        </nav>
      </aside>
      <main>
        <Outlet /> {/* Aquí se renderiza la sub-ruta activa */}
      </main>
    </div>
  );
}

// Configuración del router con layout anidado
{
  path: 'dashboard',
  element: <LayoutDashboard />,
  children: [
    { index: true, element: <ResumenDashboard /> },
    { path: 'configuracion', element: <Configuracion /> },
    { path: 'facturacion', element: <Facturacion /> },
  ],
}

Loaders — datos antes de renderizar

Los loaders son funciones async que se ejecutan antes del render:

import { useLoaderData, type LoaderFunctionArgs } from 'react-router-dom';

interface Articulo {
  id: string;
  titulo: string;
  contenido: string;
  autor: string;
}

// El loader se ejecuta antes de montar el componente
async function articuloLoader({ params }: LoaderFunctionArgs): Promise<Articulo> {
  const res = await fetch(`/api/articulos/${params.slug}`);
  if (!res.ok) throw new Response('No encontrado', { status: 404 });
  return res.json() as Promise<Articulo>;
}

function PaginaArticulo(): React.JSX.Element {
  // Los datos ya están disponibles — sin estado de carga
  const articulo = useLoaderData() as Articulo;

  return (
    <article>
      <h1>{articulo.titulo}</h1>
      <p>Por: {articulo.autor}</p>
      <div dangerouslySetInnerHTML={{ __html: articulo.contenido }} />
    </article>
  );
}

// Configuración con loader
{ path: 'blog/:slug', loader: articuloLoader, element: <PaginaArticulo /> }

Manejo de errores en rutas

import { useRouteError, isRouteErrorResponse } from 'react-router-dom';

function ErrorBoundaryRuta(): React.JSX.Element {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    return (
      <div>
        <h1>{error.status} — {error.statusText}</h1>
        <p>{error.data as string}</p>
      </div>
    );
  }

  return <h1>Error inesperado</h1>;
}

// Asignar errorElement a rutas específicas o al root
{ path: '/', element: <Layout />, errorElement: <ErrorBoundaryRuta /> }

Rutas lazy — carga perezosa

import { lazy, Suspense } from 'react';

const PanelAdmin = lazy(() => import('./pages/PanelAdmin'));

// En el router
{
  path: 'admin',
  element: (
    <Suspense fallback={<p>Cargando panel…</p>}>
      <PanelAdmin />
    </Suspense>
  ),
}

La carga perezosa divide el bundle y carga el código del panel de admin solo cuando el usuario navega a esa ruta, reduciendo el tiempo de carga inicial de la aplicación.

React Router v7 unifica framework y SPA modes
React Router v7 fusionó Remix y React Router. En modo SPA (createBrowserRouter) funciona como siempre. En modo framework (con el adaptador) agrega SSR, loaders del servidor y más. Este curso usa el modo SPA que es el más común para aplicaciones React puras.
Usa Link en vez de anchor tags para navegación interna
Nunca uses <a href=...> para navegar entre rutas internas: recargará la página completa. Usa siempre <Link to=...> para navegación SPA (sin recarga) y <NavLink> cuando necesites detectar si la ruta está activa para aplicar estilos.
Los loaders se ejecutan antes de renderizar el componente
Los loaders de React Router v7 son funciones async que se ejecutan antes de que el componente se monte. Lanza errores (throw) o redirige (redirect) desde el loader para manejar errores y autenticación. Usa useLoaderData() tipado para consumir los datos en el componente.