Rendimiento SPA: Code Splitting y Lazy Loading
Mejora tiempos de carga de single-page applications dividiendo bundles a nivel de ruta y componente, implementando lazy loading con React.lazy e imports dinamicos
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Rendimiento SPA: Code Splitting y Lazy Loading
Reduce el tamano del bundle inicial en single-page applications dividiendo codigo a nivel de ruta y componente. Esta recipe demuestra React.lazy, imports dinamicos y estrategias de preload que mantienen time-to-interactive bajo sin sacrificar experiencia de usuario.
Cuando Usar Esto
- Tu bundle de SPA excede 200KB gzip y carga lentamente en mobile
- No todas las rutas son accedidas por cada usuario en la primera visita
- Componentes pesados (graficos, editores, mapas) solo se necesitan en paginas especificas
Solucion
1. Code Splitting a Nivel de Ruta
// router.tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Reports = lazy(() => import('./pages/Reports'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/reports" element={<Reports />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
2. Lazy Loading a Nivel de Componente
// components/HeavyChart.tsx
import { lazy, Suspense, useState } from 'react';
const Chart = lazy(() => import('./ChartLibrary'));
function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>Show Analytics</button>
{showChart && (
<Suspense fallback={<ChartSkeleton />}>
<Chart data={getData()} />
</Suspense>
)}
</div>
);
}
3. Prefetch en Hover
// utils/prefetch.ts
const lazyPages = {
'/reports': () => import('./pages/Reports'),
'/analytics': () => import('./pages/Analytics'),
};
export function prefetchRoute(path: string): void {
const loader = lazyPages[path as keyof typeof lazyPages];
if (loader) loader();
}
// Navigation.tsx
import { prefetchRoute } from './utils/prefetch';
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
return (
<a
href={to}
onMouseEnter={() => prefetchRoute(to)}
>
{children}
</a>
);
}
4. Configuracion Vite para Chunking
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
charts: ['recharts'],
forms: ['react-hook-form', 'zod'],
},
},
},
},
});
Como Funciona
React.lazyenvuelve un import dinamico y renderiza un fallback mientras cargaSuspenseboundaries capturan estados de carga y muestran fallback UI- Prefetching en hover inicia la carga antes de que el usuario haga click
- Manual chunks agrupan codigo vendor compartido en bundles cacheables
Variacion: Intersection Observer para Contenido Below-Fold
// hooks/useLazyLoad.ts
import { useEffect, useRef, useState } from 'react';
function useLazyLoad() {
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return { ref, isVisible };
}
Consideraciones de Produccion
- Setea fallback UI apropiado para prevenir layout shifts mientras carga
- Monitorea Core Web Vitals (LCP, INP, CLS) despues de hacer splitting
- Usa
preloadpara rutas criticas accedidas por la mayoria de usuarios
Errores Comunes
- Envolver cada componente en lazy, causando excessive network requests
- No manejar errores de carga con un
ErrorBoundary - Olvidar que rutas lazy-loaded aun necesitan que sus datos sean fetched
FAQ
P: Funciona con SSR?
R: Si, pero usa @loadable/component en lugar de React.lazy para soporte de server-side rendering.
P: Que tan pequeno deberia ser cada chunk? R: Apunta a 30-100KB gzip por chunk de ruta. Demasiados chunks pequenos afectan rendimiento por overhead de requests.