Logo
Published on

Lazy Routes

How React Router Lazy Routes Optimize Performance

React Router Lazy Routes enable code splitting and on-demand loading of components, reducing initial bundle size and improving application startup time. They load route components only when users navigate to them, optimizing performance for large applications. Teams using lazy routes report faster load times and better user experience.

TL;DR

  • Use React.lazy() with dynamic imports for route splitting
  • Wrap lazy components in Suspense with loading fallbacks
  • Reduces initial bundle size and improves startup performance
  • Perfect for admin panels, dashboards, and large feature sections
const result = process(data)

The Bundle Size Challenge

Your application loads slowly because all route components are bundled together, forcing users to download unused code. Large features like admin panels and dashboards make the initial JavaScript bundle massive, even for users who never access those sections.

// Problematic: All route components loaded at startup
const React = require('react')
const { createBrowserRouter } = require('react-router-dom')
const HomeComponent = require('./pages/Home')
const DashboardComponent = require('./pages/Dashboard')
const AdminPanelComponent = require('./pages/AdminPanel')

const router = createBrowserRouter([
  { path: '/', element: React.createElement(HomeComponent) },
  { path: '/dashboard', element: React.createElement(DashboardComponent) },
  { path: '/admin', element: React.createElement(AdminPanelComponent) },
])

console.log('All components bundled together at startup')
console.log('Users download code for routes they never visit')
console.log('Large initial bundle size impacts loading performance')

React Router Lazy Routes split code automatically and load components on demand:

// React Router: Lazy loading with code splitting
const React = require('react')
const { Suspense } = require('react-router-dom')
const Dashboard = React.lazy(() => import('./pages/Dashboard'))

const dashboardRoute = {
  path: '/dashboard',
  element: React.createElement(
    Suspense,
    { fallback: React.createElement('div', null, 'Loading...') },
    React.createElement(Dashboard)
  ),
}

console.log('Components load only when needed - optimized bundle size')

Best Practises

Use React Router lazy routes when:

  • ✅ Building large applications with many route components
  • ✅ Creating admin panels or dashboards with heavy dependencies
  • ✅ Optimizing initial load time for better user experience
  • ✅ Implementing feature-based code splitting for better caching

Avoid when:

  • 🚩 Small applications where bundle splitting adds complexity
  • 🚩 Critical routes that users access immediately on load
  • 🚩 Components that share many common dependencies
  • 🚩 Server-side rendering where dynamic imports complicate setup

System Design Trade-offs

AspectLazy RoutesEager Loading
Initial Load TimeExcellent - smaller bundlePoor - loads all code upfront
Network EfficiencyGood - loads code as neededPoor - downloads unused features
Caching StrategyExcellent - granular cache controlPoor - invalidates entire bundle
User ExperienceGood - faster startup, loading statesPoor - slow initial load
Build ComplexityMedium - automatic code splittingLow - single bundle generation
Runtime PerformanceMedium - network requests for routesGood - all code immediately available

More Code Examples

❌ Eager loading components
// Eager loading imports all components at build time
const React = require('react')
const { createBrowserRouter } = require('react-router-dom')

// All imports loaded immediately - large bundle size
const HomePage = require('./components/HomePage')
const UserDashboard = require('./components/UserDashboard')
const AdminDashboard = require('./components/AdminDashboard')
const ReportsModule = require('./components/ReportsModule')
const SettingsPanel = require('./components/SettingsPanel')
const AnalyticsView = require('./components/AnalyticsView')
const UserManagement = require('./components/UserManagement')
const BillingSystem = require('./components/BillingSystem')

const eagerRouter = createBrowserRouter([
  { path: '/', element: React.createElement(HomePage) },
  { path: '/dashboard', element: React.createElement(UserDashboard) },
  { path: '/admin', element: React.createElement(AdminDashboard) },
  { path: '/reports', element: React.createElement(ReportsModule) },
  { path: '/settings', element: React.createElement(SettingsPanel) },
  { path: '/analytics', element: React.createElement(AnalyticsView) },
  { path: '/users', element: React.createElement(UserManagement) },
  { path: '/billing', element: React.createElement(BillingSystem) },
])

// Bundle analysis shows:
const bundleSize = {
  main: '2.3MB', // All components included
  vendor: '800KB',
  total: '3.1MB', // Users download everything upfront
}

console.log('Initial bundle size:', bundleSize.total)
console.log('Users wait for entire app to download before first interaction')
✅ Lazy loading with Suspense
// Lazy loading with automatic code splitting
const React = require('react')
const { createBrowserRouter, Suspense } = require('react-router-dom')

// Dynamic imports create separate bundles for each route
const UserDashboard = React.lazy(() => import('./components/UserDashboard'))
const AdminDashboard = React.lazy(() => import('./components/AdminDashboard'))
const ReportsModule = React.lazy(() => import('./components/ReportsModule'))
const AnalyticsView = React.lazy(() => import('./components/AnalyticsView'))

// Reusable loading component with better UX
const RouteLoader = ({ message = 'Loading...' }) =>
  React.createElement('div', { className: 'route-loader' }, [
    React.createElement('div', { className: 'spinner' }),
    React.createElement('p', null, message),
  ])

const lazyRouter = createBrowserRouter([
  { path: '/', element: React.createElement('div', null, 'Home') },
  {
    path: '/dashboard',
    element: React.createElement(
      Suspense,
      { fallback: React.createElement(RouteLoader, { message: 'Loading Dashboard...' }) },
      React.createElement(UserDashboard)
    ),
  },
  {
    path: '/admin',
    element: React.createElement(
      Suspense,
      { fallback: React.createElement(RouteLoader, { message: 'Loading Admin...' }) },
      React.createElement(AdminDashboard)
    ),
  },
  {
    path: '/reports',
    element: React.createElement(
      Suspense,
      { fallback: React.createElement(RouteLoader, { message: 'Loading Reports...' }) },
      React.createElement(ReportsModule)
    ),
  },
])

// Bundle analysis with lazy loading
const bundleSize = {
  main: '450KB',
  dashboard: '380KB',
  admin: '520KB',
  reports: '290KB',
  initialLoad: '450KB',
}

console.log('Initial bundle reduced to:', bundleSize.initialLoad)
console.log('Route chunks loaded on demand for better performance')
console.log('Improved startup time with automatic code splitting')

Technical Trivia

The Netflix Code Splitting Success of 2020: Netflix redesigned their web application using React Router lazy routes, reducing their initial bundle size from 4.2MB to 800KB. The lazy loading implementation decreased time-to-interactive by 60% and improved user engagement, especially on slower connections and mobile devices.

Why eager loading was problematic: The monolithic bundle included code for every feature - from admin panels to analytics dashboards - that most users never accessed. Mobile users on slower networks experienced 8-12 second load times before they could interact with the basic homepage.

Lazy routes transformed the experience: Route-based code splitting meant users downloaded only the code they needed. The homepage loaded in under 2 seconds, while feature-specific bundles loaded seamlessly when users navigated to them, creating a much smoother user experience with better perceived performance.


Master Lazy Routes: Implementation Strategy

Implement lazy routes for large application sections and admin features that not all users access. Always wrap lazy components in Suspense with meaningful loading states to maintain good user experience during chunk loading. Consider grouping related routes into the same chunk to avoid excessive code splitting, and use React.lazy with dynamic imports for automatic bundle optimization by your build tool.