Skip to content

React Router v7 con TypeScript

React Router es una biblioteca popular para manejar el enrutamiento en aplicaciones React. En esta guía, veremos cómo configurar y usar React Router v7 con TypeScript.

Modos de React Router

React Router ofrece tres modos principales para integrarlo en tu aplicación. Estos modos son aditivos: cada uno añade funcionalidades sobre el anterior, lo que implica también un mayor compromiso con la arquitectura del proyecto.

La elección depende de cuánto control necesitas y cuánta ayuda quieres recibir de React Router.

🟢 1. Modo Declarativo (Declarative Mode)

El modo declarativo habilita funciones de enrutamiento básicas, como hacer coincidir URL con componentes, navegar por la aplicación y proporcionar estados activos con API como <Link>, useNavigate, useLocation.

tsx
import { BrowserRouter } from "react-router";

ReactDOM.createRoot(root).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

🟡 2. Modo de Datos (Data Mode)

Añade funcionalidades para manejo de datos, separando la configuración de rutas del renderizado.

Características:

  • Permite definir:

    • loader (carga de datos),
    • action (envío de datos),
    • useFetcher (gestión de estados pendientes).
  • Mejora la experiencia del usuario con datos precargados.

Uso recomendado si:

  • Quieres un mayor control sobre datos, bundling y abstracciones del servidor.
  • Estás usando el enrutador de datos desde React Router v6.4.

Configuración:

tsx
import { createBrowserRouter, RouterProvider } from "react-router";

let router = createBrowserRouter([
  {
    path: "/",
    Component: Root,
    loader: loadRootData,
  },
]);

ReactDOM.createRoot(root).render(<RouterProvider router={router} />);

🔵 3. Modo Framework (Framework Mode)

La experiencia más completa, ideal si quieres aprovechar todo lo que ofrece React Router.

Características:

  • Basado en el Modo de Datos, pero con integración total con herramientas como Vite.

  • Ofrece:

    • Seguridad de tipos en href y módulos de ruta.

    • División inteligente de código.

    • Estrategias de renderizado:

      • SPA (Single Page Application),
      • SSR (Server Side Rendering),
      • SSG (Static Site Generation).

Uso recomendado si:

  • Eres nuevo en React Router y no tienes una arquitectura definida.
  • Estás migrando desde frameworks como Next.js o Remix.
  • Buscas una alternativa a SvelteKit o simplemente quieres "construir con React".

Configuración:

routes.ts

tsx
import { index, route } from "@react-router/dev/routes";

export default [index("./home.tsx"), route("products/:pid", "./product.tsx")];

Luego tendrá acceso a la API del módulo de ruta con parámetros de tipo seguro, loaderData, división de código, estrategias SPA/SSR/SSG y más.

product.tsx

tsx
import { Route } from "+./types/product.tsx";

export async function loader({ params }: Route.LoaderArgs) {
  let product = await getProduct(params.pid);
  return { product };
}

export default function Product({ loaderData }: Route.ComponentProps) {
  return <div>{loaderData.product.name}</div>;
}

Modo Declarativo

sh
npx create-vite@latest my-app
cd my-app
sh
npm i react-router

main.tsx

tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

import { BrowserRouter } from "react-router";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>
);
  • BrowserRouter: Componente que envuelve tu aplicación y habilita el enrutamiento basado en la URL del navegador.
  • Routes: Componente que agrupa todas las rutas de tu aplicación.
  • Route: Componente que define una ruta específica.
  • path: es la ruta de la URL que se va a coincidir.
  • element: es el componente que se renderizará cuando la ruta coincida.

App.tsx

tsx
import { Route, Routes } from "react-router";

const App = () => {
  return (
    <Routes>
      <Route
        path="/"
        element={<h1>Home Page</h1>}
      />

      {/* <Route
        index
        element={<Home />}
      /> */}

      <Route
        path="about"
        element={<h1>About Page</h1>}
      />
    </Routes>
  );
};
export default App;

Layouts

Los layouts son componentes que se utilizan para envolver otras rutas y proporcionar una estructura común a varias páginas.

src\pages\auth\auth-layout.tsx

tsx
import { Outlet } from "react-router";

const AuthLayout = () => {
  return (
    <div>
      <Outlet />
      <footer>
        <p>Auth Layout Footer</p>
      </footer>
    </div>
  );
};
export default AuthLayout;

App.tsx

tsx
import { Route, Routes } from "react-router";

import AuthLayout from "./pages/auth/auth-layout";

import Login from "./pages/auth/login";
import Register from "./pages/auth/register";

const App = () => {
  return (
    <Routes>
      ...
      <Route element={<AuthLayout />}>
        <Route
          path="login"
          element={<Login />}
        />
        <Route
          path="register"
          element={<Login />}
        />
      </Route>
    </Routes>
  );
};
export default App;

Rutas Anidadas

Las rutas anidadas permiten crear una jerarquía de rutas, donde una ruta puede contener otras rutas dentro de ella.

  • useLocation: Hook que permite acceder a la ubicación actual de la URL.

src\pages\admin\admin-layout.tsx

tsx
import { Outlet, useLocation } from "react-router";

const AdminLayout = () => {
  const location = useLocation();

  return (
    <div>
      <header>
        <p>Current Path: {location.pathname}</p>
      </header>
      <Outlet />
      <footer>
        <p>Admin Footer</p>
      </footer>
    </div>
  );
};
export default AdminLayout;

App.tsx

tsx
import { Route, Routes } from "react-router";

import AdminLayout from "./pages/admin/admin-layout";

import Dashboard from "./pages/admin/dashboard";
import Profile from "./pages/admin/profile";

const App = () => {
  return (
    <Routes>
      ...
      <Route
        path="admin"
        element={<AdminLayout />}
      >
        <Route
          index
          element={<Dashboard />}
        />
        <Route
          path="profile"
          element={<Profile />}
        />
      </Route>
    </Routes>
  );
};
export default App;

Rutas Dinámicas

Las rutas dinámicas permiten crear rutas que pueden cambiar según los parámetros de la URL.

src\pages\admin\product.tsx

tsx
import { useParams } from "react-router";

const Product = () => {
  const params = useParams();

  console.log({ params });

  return (
    <div>
      <h2>Product Page</h2>
      <p>Product ID: {params.id}</p>
      <pre>{JSON.stringify(params, null, 2)}</pre>
    </div>
  );
};
export default Product;

App.tsx

tsx
<Route
  path="admin"
  element={<AdminLayout />}
>
  ...
  <Route
    path="products/:id"
    element={<Product />}
  />
</Route>

Varios Parámetros

tsx
<Route
  path="products/:name/:id"
  element={<Product />}
/>

Parámetros Opcionales

tsx
<Route
  path="products/:name/:id?"
  element={<Product />}
/>

Splats

Los splats permiten capturar partes de la URL que no coinciden con ninguna ruta específica.

  • Link: Componente que permite crear enlaces a otras rutas. La diferencia con un enlace HTML es que Link no recarga la página, sino que actualiza la URL y renderiza el componente correspondiente.

src\pages\not-found.tsx

tsx
import { Link } from "react-router";

const NotFound = () => {
  return (
    <div>
      <h2>Page Not Found</h2>
      <p>Please check the URL or return to the home page.</p>
      <Link to="/">Go to Home</Link>
    </div>
  );
};
export default NotFound;

App.tsx

tsx
import { Route, Routes } from "react-router";

import NotFound from "./pages/not-found";

const App = () => {
  return (
    <Routes>
      ...
      <Route
        path="*"
        element={<NotFound />}
      />
    </Routes>
  );
};
export default App;

Para navegar entre rutas, puedes usar Link, NavLink y useNavigate.

NavLink es similar a Link, pero permite aplicar estilos activos a los enlaces.

  • end: Propiedad que indica si el enlace debe coincidir exactamente con la ruta.

src\components\admin-navbar.tsx

tsx
import { NavLink } from "react-router";

const AdminNavbar = () => {
  return (
    <nav>
      <ul>
        <li>
          <NavLink
            to="/admin"
            end
          >
            Dashboard
          </NavLink>
        </li>
        <li>
          <NavLink to="/admin/profile">Profile</NavLink>
        </li>
        <li>
          <NavLink to="/admin/products/product1/123">Product 1</NavLink>
        </li>
      </ul>
    </nav>
  );
};
export default AdminNavbar;

También puedes aplicar estilos activos a los enlaces utilizando la clase active:

src\index.css

css
a.active {
  color: red;
}

src\pages\admin\admin-layout.tsx

tsx
import { Outlet, useLocation } from "react-router";
import AdminNavbar from "../../components/admin-navbar";

const AdminLayout = () => {
  const location = useLocation();

  return (
    <div>
      <header>
        <p>Current Path: {location.pathname}</p>
      </header>
      <AdminNavbar />
      <Outlet />
      <footer>
        <p>Admin Footer</p>
      </footer>
    </div>
  );
};
export default AdminLayout;

isActive

Puedes usar la función isActive para aplicar estilos condicionales a los enlaces.

src\components\admin-navbar.tsx

tsx
import { NavLink } from "react-router";

const AdminNavbar = () => {
  return (
    <nav>
      <ul className="nav nav-underline">
        <li className="nav-item">
          <NavLink
            to="/admin"
            end
            className={({ isActive }) =>
              `nav-link p-2 ${
                isActive ? "active bg-secondary" : "text-secondary"
              }`
            }
            style={({ isActive }) => ({ color: isActive ? "salmon" : "black" })}
          >
            Dashboard
          </NavLink>
        </li>
        <li className="nav-item">
          <NavLink
            to="/admin/profile"
            className={({ isActive }) =>
              `nav-link p-2 ${
                isActive ? "active bg-secondary" : "text-secondary"
              }`
            }
            style={({ isActive }) => ({ color: isActive ? "salmon" : "black" })}
          >
            Profile
          </NavLink>
        </li>
        <li className="nav-item">
          <NavLink
            to="/admin/products/product1/123"
            className={({ isActive }) =>
              `nav-link p-2 ${
                isActive ? "active bg-secondary" : "text-secondary"
              }`
            }
            style={({ isActive }) => ({ color: isActive ? "salmon" : "black" })}
          >
            Product 1
          </NavLink>
        </li>
      </ul>
    </nav>
  );
};
export default AdminNavbar;

Modo mejorado:

tsx
import { NavLink } from "react-router";

// Define cada ítem del menú
type NavItem = {
  to: string;
  label: string;
  end?: boolean;
};

const navItems: NavItem[] = [
  { to: "/admin", label: "Dashboard", end: true },
  { to: "/admin/profile", label: "Profile" },
  { to: "/admin/products/product1/123", label: "Product 1" },
];

// Funciones utilitarias para clases y estilos
const getNavClass = (isActive: boolean) =>
  `nav-link p-2 ${isActive ? "active bg-secondary" : "text-secondary"}`;

const getNavStyle = (isActive: boolean) => ({
  color: isActive ? "salmon" : "black",
});

const AdminNavbar = () => {
  return (
    <nav>
      <ul className="nav nav-underline">
        {navItems.map(({ to, label, end }, index) => (
          <li
            className="nav-item"
            key={index}
          >
            <NavLink
              to={to}
              end={end}
              className={({ isActive }) => getNavClass(isActive)}
              style={({ isActive }) => getNavStyle(isActive)}
            >
              {label}
            </NavLink>
          </li>
        ))}
      </ul>
    </nav>
  );
};

export default AdminNavbar;

useNavigate

useNavigate es un hook que permite programáticamente navegar a otras rutas.

src\pages\admin\dashboard.tsx

tsx
import { useNavigate } from "react-router";

const Dashboard = () => {
  const navigate = useNavigate();

  return (
    <div>
      <h1>Admin Dashboard</h1>
      <p>Welcome to the admin dashboard!</p>
      <button
        onClick={() => navigate("/admin/profile")}
        className="btn btn-primary"
      >
        Go to Profile
      </button>
    </div>
  );
};
export default Dashboard;