How to implement lazy loading in React

Lazy loading defers component loading until they’re needed, reducing initial bundle size and improving load times. As the creator of CoreUI with 12 years of React development experience, I’ve implemented lazy loading strategies that reduced initial bundle sizes by 70% and improved Time to Interactive by 3 seconds for production applications serving millions of users.

The most effective approach uses React.lazy() with Suspense for automatic code splitting at the component level.

Basic Lazy Loading

import React, { lazy, Suspense } from 'react'

const Dashboard = lazy(() => import('./Dashboard'))
const Profile = lazy(() => import('./Profile'))
const Settings = lazy(() => import('./Settings'))

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

Route-Based Code Splitting

import React, { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

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

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

function LoadingSpinner() {
  return (
    <div className="loading-spinner">
      <div className="spinner"></div>
      <p>Loading...</p>
    </div>
  )
}

Error Boundary with Lazy Loading

import React, { Component, lazy, Suspense } from 'react'

class ErrorBoundary extends Component {
  state = { hasError: false, error: null }

  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }

  componentDidCatch(error, errorInfo) {
    console.error('Component loading error:', error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Failed to load component</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

const LazyComponent = lazy(() => import('./LazyComponent'))

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

Preloading Components

import React, { lazy, useState } from 'react'

const HeavyComponent = lazy(() => import('./HeavyComponent'))

// Preload function
const preloadHeavyComponent = () => {
  import('./HeavyComponent')
}

function App() {
  const [showComponent, setShowComponent] = useState(false)

  return (
    <div>
      <button
        onMouseEnter={preloadHeavyComponent}
        onClick={() => setShowComponent(true)}
      >
        Show Component
      </button>

      {showComponent && (
        <Suspense fallback={<div>Loading...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  )
}

Named Exports Lazy Loading

// For named exports, wrap in a default export
const UserProfile = lazy(() =>
  import('./UserProfile').then(module => ({
    default: module.UserProfile
  }))
)

function App() {
  return (
    <Suspense fallback={<div>Loading profile...</div>}>
      <UserProfile userId={123} />
    </Suspense>
  )
}
import React, { lazy, Suspense, useState } from 'react'

const Modal = lazy(() => import('./Modal'))

function App() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>
        Open Modal
      </button>

      {isOpen && (
        <Suspense fallback={<div>Loading modal...</div>}>
          <Modal onClose={() => setIsOpen(false)}>
            <h2>Modal Content</h2>
            <p>This modal was lazy loaded!</p>
          </Modal>
        </Suspense>
      )}
    </div>
  )
}

Nested Suspense Boundaries

import React, { lazy, Suspense } from 'react'

const Header = lazy(() => import('./Header'))
const Sidebar = lazy(() => import('./Sidebar'))
const Content = lazy(() => import('./Content'))

function Layout() {
  return (
    <div className="layout">
      <Suspense fallback={<div>Loading header...</div>}>
        <Header />
      </Suspense>

      <div className="main">
        <Suspense fallback={<div>Loading sidebar...</div>}>
          <Sidebar />
        </Suspense>

        <Suspense fallback={<div>Loading content...</div>}>
          <Content />
        </Suspense>
      </div>
    </div>
  )
}

Best Practice Note

This is the same lazy loading architecture we use in CoreUI’s React admin templates. Code splitting at route boundaries provides the biggest performance gains, while component-level lazy loading optimizes specific heavy features. Always wrap lazy components with Suspense, implement error boundaries, and use preloading for components users are likely to need soon.

For production applications, consider using CoreUI’s React Admin Template which includes optimized lazy loading patterns with route-based code splitting and skeleton loaders.

For related performance optimization, check out how to optimize React performance and how to use React Suspense.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to concatenate a strings in JavaScript?
How to concatenate a strings in JavaScript?

How to Clone an Object in JavaScript
How to Clone an Object in JavaScript

Maintaining Accessibility with React createPortal and aria-owns: A Complete Guide
Maintaining Accessibility with React createPortal and aria-owns: A Complete Guide

What is globalThis in JavaScript?
What is globalThis in JavaScript?

Answers by CoreUI Core Team