Lazy loading routes with React.lazy and Suspense

Maya Patel Jan 2026
1 tab
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Layout from '@/components/Layout'

// Eager load critical routes
import Home from '@/pages/Home'

// Lazy load other routes
const Posts = lazy(() => import('@/pages/Posts'))
const PostDetail = lazy(() => import('@/pages/PostDetail'))
const NewPost = lazy(() => import('@/pages/NewPost'))
const Profile = lazy(() => import('@/pages/Profile'))

// Admin routes in separate chunk
const AdminDashboard = lazy(() => import('@/pages/admin/Dashboard'))
const AdminUsers = lazy(() => import('@/pages/admin/Users'))

function App() {
  return (
    <BrowserRouter>
      <Suspense
        fallback={
          <div className="flex items-center justify-center min-h-screen">
            <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
          </div>
        }
      >
        <Routes>
          <Route element={<Layout />}>
            <Route path="/" element={<Home />} />
            <Route path="/posts" element={<Posts />} />
            <Route path="/posts/:id" element={<PostDetail />} />
            <Route path="/posts/new" element={<NewPost />} />
            <Route path="/profile" element={<Profile />} />

            {/* Admin routes */}
            <Route path="/admin/dashboard" element={<AdminDashboard />} />
            <Route path="/admin/users" element={<AdminUsers />} />
          </Route>
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

export default App
1 file · typescript Explain with highlit

Code splitting routes reduces initial bundle size and improves load times. React's lazy function dynamically imports components when routes are accessed. Wrapped in Suspense, lazy components show fallback UI while loading. I group related routes into chunks by organizing imports—admin routes load separately from public routes. The <Suspense> boundary can be at the route level or app level depending on desired granularity. Vite automatically creates separate bundles for dynamic imports. This pattern is essential for large apps where loading everything upfront would hurt performance. Users only download the code they need, when they need it.