Tutorial: construir una SPA con React y Vite paso a paso
En esta guía aprenderás a crear una Single Page Application (SPA) moderna con React y Vite, incorporando routing, estado global, peticiones a API, carga diferida y buenas prácticas para producción.
Requisitos
- Node.js 18+ y npm/pnpm/yarn instalados.
- Conocimientos básicos de JavaScript y React.
Paso 1 — Crear el proyecto
# con npm
npm create vite@latest my-spa -- --template react-swc
cd my-spa
npm install
npm run dev
Abre http://localhost:5173
para verificar.
Paso 2 — Estructura recomendada
my-spa/
├─ src/
│ ├─ components/
│ ├─ hooks/
│ ├─ pages/
│ ├─ context/
│ ├─ styles/
│ ├─ App.jsx
│ └─ main.jsx
├─ public/
└─ index.html
Paso 3 — Instalar el router
npm i react-router-dom
src/main.jsx
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./App.jsx";
import Home from "./pages/Home.jsx";
import About from "./pages/About.jsx";
import User from "./pages/User.jsx";
import NotFound from "./pages/NotFound.jsx";
import "./styles/global.css";
createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="users/:id" element={<User />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
</React.StrictMode>
);
src/App.jsx (layout + navegación)
import { Outlet, NavLink } from "react-router-dom";
export default function App() {
const link = ({ isActive }) => ({
padding: "8px 12px",
borderRadius: 8,
textDecoration: "none",
color: isActive ? "#111" : "#555",
background: isActive ? "#e5e7eb" : "transparent"
});
return (
<div style={{ maxWidth: 960, margin: "0 auto", padding: 24 }}>
<nav style={{ display: "flex", gap: 12, marginBottom: 24 }}>
<NavLink style={link} to="/" end>Inicio</NavLink>
<NavLink style={link} to="/about">Acerca</NavLink>
<NavLink style={link} to="/users/1">Usuario demo</NavLink>
</nav>
<Outlet />
</div>
);
}
Páginas básicas
src/pages/Home.jsx
export default function Home() {
return (<h1>Bienvenido a la SPA con React + Vite</h1>);
}
src/pages/About.jsx
export default function About() {
return (<p>Esta es una SPA creada con Vite, React y React Router.</p>);
}
src/pages/NotFound.jsx
export default function NotFound() {
return (<h2>404 — Página no encontrada</h2>);
}
Paso 4 — Consumir una API con un hook
Creamos una variable de entorno y un hook reutilizable.
.env.local
VITE_API_URL=https://jsonplaceholder.typicode.com
src/hooks/useFetch.js
import { useEffect, useState } from "react";
export function useFetch(path) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const url = `${import.meta.env.VITE_API_URL}${path}`;
(async () => {
try {
setLoading(true);
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`Error ${res.status}`);
setData(await res.json());
} catch (e) {
if (e.name !== "AbortError") setError(e);
} finally {
setLoading(false);
}
})();
return () => controller.abort();
}, [path]);
return { data, loading, error };
}
src/pages/User.jsx
import { useParams } from "react-router-dom";
import { useFetch } from "../hooks/useFetch";
export default function User() {
const { id } = useParams();
const { data: user, loading, error } = useFetch(`/users/${id}`);
if (loading) return <p>Cargando...</p>;
if (error) return <p>Hubo un error: {error.message}</p>;
return (
<article>
<h2>Usuario #{user.id}</h2>
<p><strong>Nombre:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
</article>
);
}
Paso 5 — Estado global con Context (ejemplo)
src/context/ThemeContext.jsx
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggle = () => setTheme(t => (t === "light" ? "dark" : "light"));
return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}
export const useTheme = () => useContext(ThemeContext);
En main.jsx envuelve el árbol:
import { ThemeProvider } from "./context/ThemeContext.jsx";
/* ... */
createRoot(document.getElementById("root")).render(
<React.StrictMode>
<ThemeProvider>
<BrowserRouter>{/* rutas */}</BrowserRouter>
</ThemeProvider>
</React.StrictMode>
);
Paso 6 — Code splitting (carga diferida)
import { lazy, Suspense } from "react";
const About = lazy(() => import("./pages/About.jsx"));
/* en tus Routes */
<Route path="about" element={
<Suspense fallback={<p>Cargando sección...</p>}>
<About />
</Suspense>
} />
Paso 7 — Estilos
- CSS Modules (nativo en Vite) o Tailwind CSS si prefieres utilidades.
- Importa un
global.css
con reglas base y variables.
Paso 8 — Buenas prácticas
- ESLint + Prettier (
npm i -D eslint prettier
). - Rutas relativas estables y alias en
vite.config.js
. - Manejo de errores y estados vacíos en cada vista.
Paso 9 — Build y despliegue
npm run build
npm run preview
- El
build
queda en/dist
. - Para SPAs, configura el fallback a
/index.html
en tu hosting/CDN (Netlify: archivo_redirects
con/* /index.html 200
).
Conclusión
Con Vite y React puedes levantar una SPA rápida, modular y lista para producción. Añadiendo router, hooks para datos, contexto y carga diferida, obtienes una base sólida para cualquier proyecto profesional.
👉 ¿Quieres que creemos tu SPA con React y Vite de forma profesional?
Solicítalo aquí.