Tutorial: construir una SPA con React y Vite paso a paso

Apoya mi contenido: 

Tabla de contenido

Apoya mi contenido: 

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í.

Artículos relacionados

¡Comunícate con nosotros!