}>
More polished fallback improves perceived performance:
function LoadingSpinner() {
return (
);
}
}>
How to handle errors with error bounds
React.lazy() And Suspense Do not handle loading errors (for example, network failures or missing fragments). For this, you need an error boundary.
are components of the ErrorBoundaries class that use componentDidCatch or static getDerivedStateFromError To catch errors in your child tree and present a fallback UI.
Here is a simple error threshold:
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || Something went wrong.
;
}
return this.props.children;
}
}
Wrap yourself up. Suspense Range with margin of error:
import { lazy, Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';
const HeavyChart = lazy(() => import('./HeavyChart'));
function App() {
return (
Failed to load chart. Please try again.}>
Loading chart...}>
);
}
If the part fails to load, the error boundary catches it and displays your fallback instead of a blank screen or an unhandled error.
How to use next/dynamic In Next.js
Next.js provides next/dynamicwhich wraps. React.lazy() And Suspense and adds options optimized for Next.js (including server-side rendering).
Main use:
'use client';
import dynamic from 'next/dynamic';
const ComponentA = dynamic(() => import('../components/A'));
const ComponentB = dynamic(() => import('../components/B'));
export default function Page() {
return (
);
}
Custom loading UI
use loading Option to show placeholder while loading component:
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
loading: () => Loading chart...
,
});
Disable server-side rendering.
For components that should only run on the client (eg, users window or browser-only APIs), set ssr: false:
const ClientOnlyMap = dynamic(() => import('../components/Map'), {
ssr: false,
loading: () => Loading map...
,
});
Note: ssr: false Works only for client components. Use it in a 'use client' File
Load on demand.
You can load a component only if a condition is met:
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
const Modal = dynamic(() => import('../components/Modal'), {
loading: () => Opening modal...
,
});
export default function Page() {
const (showModal, setShowModal) = useState(false);
return (
{showModal && setShowModal(false)} />}
);
}
Exports were named.
For named exports, return the component from the dynamic import:
const Hello = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
);
Using Suspense with Next/Active
In React 18+, you can use suspense: true Trusting parents Suspense Limit instead of loading option:
const HeavyChart = dynamic(() => import('../components/HeavyChart'), {
suspense: true,
});
// In your component:
Loading...}>
Important: When using suspense: trueyou cannot use ssr: false or loading Use the option. Suspense Fallback instead.
React.lazy Vs next/dynamic: When to use each.
| Feature | React.lazy + suspense | Next/Active |
|---|---|---|
| Framework | Any React app (Create React App, White etc.) | Next.js only |
| Server-side rendering | Not supported. | Supported by default |
| Disable SSR. | N/A | ssr: false option |
| Loading UI. | Suspense Fallback support | Built-in loading option |
| Handling error | An error boundary is required. | An error boundary is required. |
| Name exports | manual .then() Mapping | the same .then() The pattern |
| Suspense mode | Always uses suspense. | Optional through suspense: true |
When to use React.lazy
You are building one. Pure React App (not Next.js)
You use the Create React app, Vite or a custom Webpack setup.
You don’t need server-side rendering.
You want a simple, framework-agnostic approach.
when Use next/dynamic
You are building one. Next.js app
You need SSR for some components and want to disable it for others.
You want the built-in loading placeholders without adding them manually.
SuspenseYou want Next.js specific optimizations and defaults.
Real world examples.
Example 1: Route-based code distribution in React
Divide your app by root so that each page loads only when the user navigates to it:
// App.jsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
Failed to load page.}>
Loading page...}>
} />
} />
} />
);
}
Example 2: Slow loading of heavy chart library in Next.js.
Delay loading the chart library until the user opens the analytics section:
// app/analytics/page.jsx
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('../components/Chart'), {
ssr: false,
loading: () => (
),
});
export default function AnalyticsPage() {
const (showChart, setShowChart) = useState(false);
return (
{showChart && }
);
}
Example 3: Slow loading of models.
Load a modal component only when the user clicks to open it:
// React (with React.lazy)
import { lazy, Suspense, useState } from 'react';
const Modal = lazy(() => import('./Modal'));
function ProductPage() {
const (showModal, setShowModal) = useState(false);
return (
{showModal && (
setShowModal(false)} />
)}
);
}
// Next.js (with next/dynamic)
'use client';
import { useState } from 'react';
import dynamic from 'next/dynamic';
const Modal = dynamic(() => import('./Modal'), {
loading: () => null,
});
export default function ProductPage() {
const (showModal, setShowModal) = useState(false);
return (
{showModal && setShowModal(false)} />}
);
}
Example 4: Lazy loading external libraries
Load the library only when the user needs it (for example, when they start typing in the search box):
'use client';
import { useState } from 'react';
const names = ('Alice', 'Bob', 'Charlie', 'Diana');
export default function SearchPage() {
const (results, setResults) = useState(());
const (query, setQuery) = useState('');
const handleSearch = async (value) => {
setQuery(value);
if (!value) {
setResults(());
return;
}
// Load fuse.js only when user searches
const Fuse = (await import('fuse.js')).default;
const fuse = new Fuse(names);
setResults(fuse.search(value));
};
return (
);
}
The result
Lazy loading improves performance by splitting your bundle and loading code only when needed. Here’s what you learned:
React.lazy() – Use simple React apps for code distribution. It requires default export and works with dynamic.
import().Suspense – Wrap the loose ingredients.
Suspenseand provide afallbackFor the loading condition.Limits of error – Use them to catch partial load failures and display a friendly error UI.
Next/Active – Use in Next.js for the same benefits plus SSR control and built-in loading options.
select React.lazy For reaction-only projects and next/dynamic Connect with them for Next.js. Suspense and error bounds for solid slow loading setups.
Start by identifying your heaviest components (charts, models, admin panels) and load them slowly. Measure your bundle size and core web vitals before and after you see the effect.
If you read this far, thank the author for letting them know you care.
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people land jobs as developers. start
