Articulo publicado el
Guía práctica para iniciar un proyecto con Vite, configurar rutas, layouts, páginas dinámicas y una pantalla 404
Crear una aplicación con React es bastante directo, pero en cuanto el proyecto necesita varias páginas aparece una pregunta importante: ¿cómo navegamos entre ellas sin recargar completamente el navegador?
React se encarga de construir la interfaz. React Router añade el sistema de rutas que conecta una URL con una pantalla concreta y permite navegar dentro de una SPA, o Single Page Application.
En esta guía vamos a crear una aplicación desde cero con:
Antes de empezar necesitas tener instalados:
Puedes comprobar Node.js y npm desde la terminal:
node --version
npm --version
Vite proporciona un entorno de desarrollo rápido y una configuración inicial pequeña. Ejecuta:
npm create vite@latest mi-app-react -- --template react
Entra en la carpeta e instala las dependencias:
cd mi-app-react
npm install
Inicia el servidor de desarrollo:
npm run dev
Vite mostrará en la terminal la URL local de la aplicación, normalmente http://localhost:5173.
Instala React Router dentro del proyecto:
npm install react-router
En este ejemplo usaremos el modo declarativo. Es una buena opción para aprender el sistema de rutas y para aplicaciones que no necesitan loaders o actions asociados a cada ruta.
Vamos a separar las páginas, el layout y la configuración principal:
src/
├── layouts/
│ └── RootLayout.jsx
├── pages/
│ ├── HomePage.jsx
│ ├── AboutPage.jsx
│ ├── ProjectPage.jsx
│ └── NotFoundPage.jsx
├── App.jsx
├── main.jsx
└── index.css
No es una estructura obligatoria, pero evita que App.jsx termine conteniendo toda la interfaz del proyecto.
BrowserRouterAbre src/main.jsx y añade BrowserRouter alrededor de la aplicación:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
BrowserRouter mantiene sincronizada la interfaz con la URL utilizando la API History del navegador.
Solo debe existir un router principal envolviendo esta parte de la aplicación. No es necesario añadir otro BrowserRouter dentro de cada página.
Empezamos con una página de inicio:
// src/pages/HomePage.jsx
export default function HomePage() {
return (
<section>
<h1>Inicio</h1>
<p>Esta es la página principal de la aplicación.</p>
</section>
);
}
Creamos también la página sobre el proyecto:
// src/pages/AboutPage.jsx
export default function AboutPage() {
return (
<section>
<h1>Sobre nosotros</h1>
<p>Esta aplicación utiliza React y React Router.</p>
</section>
);
}
Y una página para las URLs que no existen:
// src/pages/NotFoundPage.jsx
import { Link } from "react-router";
export default function NotFoundPage() {
return (
<section>
<h1>Página no encontrada</h1>
<p>La dirección solicitada no existe.</p>
<Link to="/">Volver al inicio</Link>
</section>
);
}
Un layout permite reutilizar la cabecera, la navegación y el pie de página entre diferentes rutas.
// src/layouts/RootLayout.jsx
import { NavLink, Outlet } from "react-router";
export default function RootLayout() {
return (
<div className="app-shell">
<header>
<a href="#main-content" className="skip-link">
Saltar al contenido
</a>
<nav aria-label="Navegación principal">
<NavLink to="/" end>
Inicio
</NavLink>
<NavLink to="/sobre-nosotros">Sobre nosotros</NavLink>
</nav>
</header>
<main id="main-content">
<Outlet />
</main>
<footer>Mi aplicación React</footer>
</div>
);
}
Outlet indica el lugar exacto donde React Router debe renderizar la ruta hija activa.
Usamos NavLink en la navegación porque permite identificar el enlace activo. Para enlaces internos no conviene utilizar un <a href> convencional, ya que provocaría una nueva carga del documento.
Ahora conectamos cada URL con su página desde src/App.jsx:
import { Route, Routes } from "react-router";
import RootLayout from "./layouts/RootLayout.jsx";
import AboutPage from "./pages/AboutPage.jsx";
import HomePage from "./pages/HomePage.jsx";
import NotFoundPage from "./pages/NotFoundPage.jsx";
import ProjectPage from "./pages/ProjectPage.jsx";
export default function App() {
return (
<Routes>
<Route element={<RootLayout />}>
<Route index element={<HomePage />} />
<Route path="sobre-nosotros" element={<AboutPage />} />
<Route path="proyectos/:projectId" element={<ProjectPage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}
La ruta con index se muestra cuando coincide la ruta padre, en este caso /.
La ruta * captura cualquier URL que no haya coincidido antes y muestra la página 404.
Las rutas dinámicas permiten representar distintos recursos con una misma página. En /proyectos/:projectId, el segmento projectId cambia según el proyecto solicitado.
// src/pages/ProjectPage.jsx
import { Link, useParams } from "react-router";
export default function ProjectPage() {
const { projectId } = useParams();
return (
<section>
<h1>Proyecto {projectId}</h1>
<p>Estás viendo el detalle del proyecto con ID {projectId}.</p>
<Link to="/">Volver al inicio</Link>
</section>
);
}
Si visitas /proyectos/portfolio, useParams() devolverá:
{
projectId: "portfolio";
}
Los parámetros llegan como cadenas de texto. Si representan un número, debes validarlos y convertirlos antes de usarlos.
NavLink puede generar una clase en función de si la ruta está activa:
<NavLink to="/sobre-nosotros" className={({ isActive }) => (isActive ? "nav-link active" : "nav-link")}>
Sobre nosotros
</NavLink>
Después puedes definir los estilos en src/index.css:
.nav-link {
color: #555;
text-decoration: none;
}
.nav-link:hover {
color: #111;
}
.nav-link.active {
color: #111;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.3rem;
}
No dependas únicamente del color para comunicar qué ruta está activa. El peso o el subrayado ayudan a que el estado sea más evidente.
Para la navegación visible utiliza Link o NavLink. Cuando la navegación depende de una acción, como completar correctamente un formulario, puedes usar useNavigate.
import { useNavigate } from "react-router";
export default function LoginForm() {
const navigate = useNavigate();
function handleSubmit(event) {
event.preventDefault();
const loginWasSuccessful = true;
if (loginWasSuccessful) {
navigate("/perfil");
}
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Iniciar sesión</button>
</form>
);
}
No uses useNavigate para reemplazar enlaces normales. Un enlace conserva mejor la semántica, la accesibilidad y las funciones habituales del navegador.
Durante el desarrollo:
npm run dev
Para crear la versión de producción:
npm run build
Vite generará los archivos optimizados dentro de dist/.
Puedes comprobar esa compilación localmente con:
npm run preview
BrowserRouter utiliza URLs reales como /sobre-nosotros o /proyectos/portfolio.
Si alguien abre directamente una de esas URLs, el servidor debe devolver index.html para que React Router resuelva la ruta en el navegador. Sin esa regla de fallback, es habitual que la portada funcione y que las rutas internas devuelvan un error 404 al recargar.
Servicios como Vercel, Netlify, Cloudflare Pages, Nginx o Apache permiten configurar este comportamiento, aunque la sintaxis cambia según la plataforma.
Outlet para la estructura compartida.Link y NavLink para navegación interna.useNavigate para navegaciones provocadas por acciones.* para controlar las páginas no encontradas.useParams.lazy cuando el proyecto empiece a crecer.Con esta configuración ya tienes una base clara para construir una aplicación React con varias páginas sin recargas completas.
React se ocupa de la interfaz, React Router relaciona cada URL con su pantalla y el layout evita repetir la estructura general. A partir de aquí puedes añadir autenticación, rutas protegidas, carga de datos y división de código según las necesidades del proyecto.
Estos articulos pueden interesarte si te gusto este articulo.
Aprende a construir un sistema de tooltips flexible, accesible y controlable sin depender de librerías externas.
Un componente accesible, reutilizable y escalable con API programatica, teclado, ARIA y registro interno
Configurando y creando un componente para resaltar la sintaxis de código en un blog utilizando Shiki y Next.js con server-side rendering.