Performance optimization - lazy loading and code splitting

Alex Chang Feb 2026
2 tabs
import React, { lazy, Suspense, useState, useEffect } from 'react';

// 1. Component lazy loading
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const AdminPanel = lazy(() => import('./AdminPanel'));
const Dashboard = lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

// 2. Route-based code splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function AppRouter() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// 3. Conditional component loading
function ConditionalLoad() {
  const [showChart, setShowChart] = useState(false);
  const [ChartComponent, setChartComponent] = useState(null);

  const loadChart = async () => {
    const module = await import('./Chart');
    setChartComponent(() => module.default);
    setShowChart(true);
  };

  return (
    <div>
      {!showChart && (
        <button onClick={loadChart}>Load Chart</button>
      )}
      {showChart && ChartComponent && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <ChartComponent />
        </Suspense>
      )}
    </div>
  );
}

// 4. Image lazy loading
function ImageGallery({ images }) {
  return (
    <div>
      {images.map((img, index) => (
        <img
          key={index}
          src={img.src}
          alt={img.alt}
          loading="lazy"
          width={img.width}
          height={img.height}
        />
      ))}
    </div>
  );
}

// 5. Custom lazy loading with Intersection Observer
function LazyImage({ src, alt, placeholder }) {
  const [imageSrc, setImageSrc] = useState(placeholder);
  const [imageRef, setImageRef] = useState();

  useEffect(() => {
    if (!imageRef) return;

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setImageSrc(src);
            observer.unobserve(entry.target);
          }
        });
      },
      {
        rootMargin: '50px', // Load 50px before entering viewport
      }
    );

    observer.observe(imageRef);

    return () => {
      if (imageRef) {
        observer.unobserve(imageRef);
      }
    };
  }, [imageRef, src]);

  return (
    <img
      ref={setImageRef}
      src={imageSrc}
      alt={alt}
      style={{ transition: 'opacity 0.3s' }}
    />
  );
}

// 6. Prefetching for better UX
function PrefetchExample() {
  const prefetchComponent = () => {
    const component = import('./HeavyComponent');
    // Component is now cached
  };

  return (
    <button
      onMouseEnter={prefetchComponent}
      onClick={() => setShowComponent(true)}
    >
      Hover to prefetch, click to show
    </button>
  );
}

// 7. Dynamic imports with error handling
async function loadModuleWithRetry(importFn, retries = 3) {
  try {
    return await importFn();
  } catch (error) {
    if (retries === 0) throw error;
    console.log(`Retrying import... (${retries} attempts left)`);
    await new Promise(resolve => setTimeout(resolve, 1000));
    return loadModuleWithRetry(importFn, retries - 1);
  }
}

// Usage
const LazyComponentWithRetry = lazy(() =>
  loadModuleWithRetry(() => import('./UnreliableComponent'))
);

// 8. Component-level memoization
const ExpensiveComponent = React.memo(({ data }) => {
  console.log('ExpensiveComponent rendered');
  // Heavy computation
  const result = expensiveCalculation(data);
  return <div>{result}</div>;
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.data.id === nextProps.data.id;
});

// 9. Virtual scrolling for long lists
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}
2 files · javascript Explain with highlit

Performance optimization reduces load times and improves user experience. I use code splitting to break bundles into smaller chunks loaded on demand. React's lazy() and Suspense enable component-level code splitting. Dynamic import() loads modules asynchronously. Image lazy loading defers off-screen images with loading="lazy" attribute. Intersection Observer API detects when elements enter viewport for custom lazy loading. Bundle analysis tools like webpack-bundle-analyzer identify large dependencies. Tree shaking eliminates unused code. Route-based splitting loads pages only when navigated to. Proper optimization dramatically improves Core Web Vitals and user experience.