How to fix Unhandled error thrown by lazy-loaded component in React
When using React.lazy() for code splitting, errors during chunk loading or component initialization are not caught unless wrapped in an Error Boundary. This leads to unhandled promise rejections and white-screen failures.
Quick fix
Fixes Unhandled error thrown by lazy-loaded component
import React from 'react';
interface ErrorBoundaryProps {
children: React.ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px' }}>
<h2>Something went wrong</h2>
<p>{this.state.error?.message || 'Failed to load this component'}</p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;- Create a class-based Error Boundary component
- Wrap lazy components with ErrorBoundary above Suspense
- Handle lazy loading errors specifically with enhanced fallback
- Create granular error boundaries for different sections
import React from 'react';interface ErrorBoundaryProps {children: React.ReactNode;}interface ErrorBoundaryState {hasError: boolean;error: Error | null;}class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {constructor(props: ErrorBoundaryProps) {super(props);this.state = { hasError: false, error: null };}static getDerivedStateFromError(error: Error): ErrorBoundaryState {return { hasError: true, error };}componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {console.error('Error caught by boundary:', error, errorInfo);}render() {if (this.state.hasError) {return (<div style={{ padding: '20px' }}><h2>Something went wrong</h2><p>{this.state.error?.message || 'Failed to load this component'}</p></div>);}return this.props.children;}}export default ErrorBoundary;Unhandled error thrown by lazy-loaded component
What this error means
React.lazy() enables code splitting by asynchronously loading component chunks. When the Promise rejects—due to network failures, missing chunks, or component errors—React throws the error for the nearest Error Boundary to handle. Without an Error Boundary, unhandled errors will crash your application with no graceful fallback. Error Boundaries are class components (or hooks wrappers) that catch errors thrown in child components. For lazy-loaded components, they catch both chunk-loading failures and rendering errors that occur after the component loads.
Typical symptoms
- Application crashes with white screen when lazy component fails to load
- Console shows: 'Uncaught Error: ...' or 'Uncaught (in promise) Error: ...'
- Network errors during chunk loading crash the entire page
- Component rendering errors from lazy imports are not caught
- No fallback UI displayed when chunk fetch fails
Common causes
- Lazy component is not wrapped in an Error Boundary component
- Suspense fallback is used but Error Boundary is missing
- Error Boundary is placed too high in the tree and catches too broadly
- Error Boundary is placed below the Suspense boundary instead of above
- Using functional components with try-catch instead of Error Boundary class component
How to fix "Unhandled error thrown by lazy-loaded component"
Error Boundaries must be class components. Create one to wrap your lazy components:
import React from 'react';
interface ErrorBoundaryProps {
children: React.ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px' }}>
<h2>Something went wrong</h2>
<p>{this.state.error?.message || 'Failed to load this component'}</p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;Always place ErrorBoundary outside (above) your Suspense component to catch both loading and rendering errors:
import React, { Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';
// Lazy load a component
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
</ErrorBoundary>
);
}
export default App;The nesting order is critical: ErrorBoundary → Suspense → Lazy component.
You can create a specialized error boundary for lazy routes that provides better feedback:
class LazyErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
// Check if this is a chunk loading error
const isChunkLoadError = /loading chunk/.test(error.message);
return { hasError: true, error, isChunkLoadError };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
if (/loading chunk/.test(error.message)) {
console.error('Chunk loading failed:', error);
} else {
console.error('Component error:', error);
}
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<h2>Failed to load this page</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => window.location.reload()}>
Reload page
</button>
</div>
);
}
return this.props.children;
}
}Instead of one global error boundary, create multiple boundaries at different levels to isolate failures:
function Dashboard() {
const UserPanel = React.lazy(() => import('./UserPanel'));
const Analytics = React.lazy(() => import('./Analytics'));
const Settings = React.lazy(() => import('./Settings'));
return (
<div>
<header>My Dashboard</header>
<ErrorBoundary>
<Suspense fallback={<Skeleton />}>
<UserPanel />
</Suspense>
</ErrorBoundary>
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Analytics />
</Suspense>
</ErrorBoundary>
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Settings />
</Suspense>
</ErrorBoundary>
</div>
);
}This way, if one lazy section fails, the others still render normally.
Test your error boundary during development to ensure it catches errors properly:
// In your lazy component, simulate an error
const HeavyComponent = () => {
// Force an error during development
if (process.env.NODE_ENV === 'development' && Math.random() > 0.99) {
throw new Error('Test error from lazy component');
}
return <div>Component loaded successfully</div>;
};
export default HeavyComponent;
// Or test the boundary with this pattern
function TestError() {
const [shouldError, setShouldError] = React.useState(false);
if (shouldError) {
throw new Error('Intentional test error');
}
return (
<div>
<button onClick={() => setShouldError(true)}>
Trigger Error
</button>
</div>
);
}Advanced notes
Multiple Error Boundaries: You can nest error boundaries at different tree levels. An inner boundary will catch errors within its scope, allowing outer boundaries to catch errors from other parts of the app. This is useful for large applications where you want granular error handling.
Chunk Loading Errors vs Component Errors: Network failures that prevent chunks from loading and errors thrown after a component renders are both caught by Error Boundaries. You can distinguish between them in componentDidCatch by checking the error message for patterns like "loading chunk" or "Failed to fetch dynamically imported module".
What Error Boundaries Don't Catch: Error boundaries do NOT catch:
- Errors in event handlers (use try-catch in handlers)
- Asynchronous code (use try-catch with async/await)
- Server-side rendering errors
- Errors in the Error Boundary itself
- Errors in setTimeout/setInterval callbacks
React Router Integration: When using React Router with lazy route components, place ErrorBoundary at the route level:
const routes = [
{
path: '/dashboard',
element: (
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
),
},
];Fallback UI vs Error Boundary: Suspense.fallback shows loading UI while the chunk is being fetched. Error Boundary shows error UI if the fetch fails. Both are needed for complete coverage.
Frequently asked questions
- What does "Unhandled error thrown by lazy-loaded component" mean?
- lazy() enables code splitting by asynchronously loading component chunks. When the Promise rejects—due to network failures, missing chunks, or component errors—React throws the error for the nearest Error Boundary to handle. Without an Error Boundary, unhandled errors will crash your application with no graceful fallback.
- How do I fix "Unhandled error thrown by lazy-loaded component"?
- When using React.lazy() for code splitting, errors during chunk loading or component initialization are not caught unless wrapped in an Error Boundary. This leads to unhandled promise rejections and white-screen failures.
- What causes "Unhandled error thrown by lazy-loaded component"?
- Lazy component is not wrapped in an Error Boundary component Suspense fallback is used but Error Boundary is missing Error Boundary is placed too high in the tree and catches too broadly
- Is the "Unhandled error thrown by lazy-loaded component" error serious?
- Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.