react-code-splitting-lazy-suspense

Code Splitting en React con Lazy y Suspense

  • 4 min

En el capítulo anterior optimizamos la velocidad de ejecución de nuestros componentes. Hoy vamos a optimizar la velocidad de carga inicial.

Por defecto, cuando construimos una aplicación React (usando Vite, Webpack o Create React App), la herramienta de construcción coge todos nuestros cientos de archivos JavaScript y los mete en una licuadora para crear un único archivo gigante (generalmente llamado index.js o bundle.js).

Esto tiene un problema grave: El usuario tiene que descargar TODA la aplicación para ver la primera pantalla.

Si vuestra app tiene un “Panel de Administración” muy pesado que solo usa el 1% de los usuarios, ¿por qué obligar al 99% restante a descargar el código de ese panel?

La solución es el Code Splitting. La idea es trocear ese archivo gigante en pedacitos pequeños y cargarlos solo cuando el usuario los necesite.

React.lazy: La importación dinámica

En JavaScript estándar, estamos acostumbrados a las importaciones estáticas al principio del archivo:

// ❌ Importación Estática: Se descarga SIEMPRE, se use o no.
import PanelAdmin from './pages/PanelAdmin';
Copied!

React nos ofrece una función llamada lazy que nos permite definir un componente que se importará dinámicamente.

import { lazy } from 'react';

// ✅ Importación Dinámica: Solo se descarga cuando React intente renderizarlo.
const PanelAdmin = lazy(() => import('./pages/PanelAdmin'));
Copied!

Fijaos en la sintaxis import(). Es una función que devuelve una Promesa. Vite detectará esto automáticamente y, en lugar de meter PanelAdmin en el bundle principal, creará un archivo separado (ej: PanelAdmin-XyZ.js) que se quedará en el servidor esperando a ser llamado.

Suspense: Gestionando la espera

Como la descarga del archivo PanelAdmin-XyZ.js por la red tarda un tiempo (milésimas o segundos), React se pregunta:

¿Qué narices pinto en la pantalla mientras llega el componente?

Si intentáis renderizar un componente lazy sin más, React lanzará un error y la app explotará.

Debemos envolver el componente perezoso dentro de un componente padre llamado Suspense.

Suspense acepta una prop obligatoria llamada fallback (plan b), que es lo que React pintará mientras espera.

import { lazy, Suspense } from 'react';

// 1. Definimos el componente lazy
const HeavyWidget = lazy(() => import('./components/HeavyWidget'));

export default function App() {
  return (
    <div>
      <h1>Mi Dashboard</h1>
      
      {/* 2. Envolvemos con Suspense y definimos el loading */}
      <Suspense fallback={<div className="spinner">Cargando widget...</div>}>
        <HeavyWidget />
      </Suspense>
    </div>
  );
}
Copied!

Patrón de Rutas (Route-based Splitting)

Aunque podéis hacer lazy loading de un botón o un modal, el lugar donde esta técnica suele tener más sentido es en el Router.

Queremos que cada página (/home, /about, /contact) sea un “chunk” separado. Así, la carga inicial de la web es instantánea porque solo descargamos la Home.

Vamos a refactorizar nuestro App.jsx:

import { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router';

// 1. Transformamos los imports estáticos en lazy
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard')); // Supongamos que este pesa 2MB

function App() {
  return (
    <div>
      <Navbar />
      
      {/* 2. Envolvemos TODAS las rutas en un solo Suspense */}
      <Suspense fallback={<h3>Cargando página... ⏳</h3>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </div>
  );
}
Copied!

Ahora, si abrís la pestaña “Network” del navegador:

  1. Al entrar en /, solo se baja el JS principal y el de Home.
  2. Al hacer clic en “Dashboard”, veréis una nueva petición de red bajando el código del Dashboard, aparecerá el fallback un instante, y luego la página.

UX: Evitando el “Flash” de carga

El Code Splitting es genial para el rendimiento, pero puede ser malo para la Experiencia de Usuario (UX) si abusamos de él.

Si ponéis lazy en componentes muy pequeños o que cargan muy rápido, el usuario verá un parpadeo constante de spinners (Flash of Loading Content).

Consejos prácticos

  1. Divide por Rutas: Es lo más seguro y efectivo.
  2. Divide componentes MUY pesados: Mapas interactivos, editores de texto rico, librerías de gráficos (Chart.js / D3), o modales complejos que el usuario quizás nunca abra.
  3. No dividas componentes “Above the Fold”: No cargues perezosamente el Header o el Banner principal, porque el usuario verá la web rota los primeros milisegundos.