This error occurs when React.lazy receives an invalid component instead of a Promise that resolves to a function component. Common causes include missing default exports, returning objects instead of components, or incorrect import syntax. The fix involves ensuring the lazily-loaded module exports a valid React component as its default export.
React.lazy is designed to dynamically import and load React components on-demand. It expects a function that returns a Promise (typically from a dynamic import statement) that resolves to a module with a valid React component as its default export. When React.lazy receives something that doesn't resolve to a valid component—such as an object, a plain function, a named export, or a non-component value—it throws this error. The component resolver sees that the resolved value is not a proper React component type (a function, memo, or forwardRef wrapper). The error prevents the lazy-loaded component from being rendered because React cannot determine how to display something that isn't a recognized component.
React.lazy requires the lazily-loaded module to export a React component as its default export:
// ✓ Correct: default export of component
export default function MyComponent() {
return <div>Hello</div>;
}
// ✓ Correct: named export reassigned as default
function MyComponent() {
return <div>Hello</div>;
}
export default MyComponent;
// ✗ Wrong: named export only
export function MyComponent() {
return <div>Hello</div>;
}
// ✗ Wrong: exports object or other value
export default { component: MyComponent };Ensure the component file uses one of the correct patterns with a default export.
Verify that the dynamic import path is correct and points to the component file:
import { lazy, Suspense } from 'react';
// ✓ Correct: relative path to component file
const MyComponent = lazy(() => import('./components/MyComponent'));
// ✓ Correct: path to directory with index.tsx
const Dashboard = lazy(() => import('./pages/Dashboard'));
// ✗ Wrong: path to non-existent file
const BadComponent = lazy(() => import('./BadPath/Component'));
// ✗ Wrong: importing an object or non-component
const NotComponent = lazy(() => import('./utils/helpers'));Double-check that the path resolves to an actual component file and that the file exists.
If you need to lazy-load a named export, wrap it in an intermediate function:
import { lazy } from 'react';
// Component with named export
// components/MyComponent.tsx
export function MyComponent() {
return <div>Hello</div>;
}
// Wrap named export in default export at import time
const MyComponent = lazy(() =>
import('./components/MyComponent').then(module => ({
default: module.MyComponent
}))
);This pattern converts the named export to a default export that React.lazy can use.
Ensure the imported module contains an actual React component, not just utilities or helper functions:
import { lazy } from 'react';
// ✓ Correct: actual React component
// pages/Dashboard.tsx
export default function Dashboard() {
return <main>Dashboard</main>;
}
// ✗ Wrong: helper function, not a component
// utils/helper.ts
export default function formatDate(date) {
return date.toLocaleDateString();
}
// If lazy()'ing the helper:
const Dashboard = lazy(() => import('./pages/Dashboard')); // OK
const Helper = lazy(() => import('./utils/helper')); // ERRORMake sure you're lazily loading actual React components, not utility functions.
React.lazy expects a function that returns a Promise, not a Promise directly:
import { lazy } from 'react';
// ✓ Correct: function returning Promise
const MyComponent = lazy(() => import('./MyComponent'));
// ✗ Wrong: direct Promise
const MyComponent = lazy(import('./MyComponent')); // ERROR
// ✗ Wrong: async function (returns Promise)
const MyComponent = lazy(async () => {
return import('./MyComponent');
});
// ✓ Correct: async function with proper import
const MyComponent = lazy(() => import('./MyComponent'));Pass a function to lazy(), not a Promise or direct import statement.
Always render lazy components inside a Suspense boundary with a fallback:
import { lazy, Suspense } from 'react';
const MyComponent = lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}The Suspense boundary handles the loading state and provides a fallback UI while the component is being imported. This also helps isolate errors if they occur.
Use an Error Boundary to gracefully handle errors during lazy loading:
import { lazy, Suspense, Component } from 'react';
const MyComponent = lazy(() => import('./MyComponent'));
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Lazy loading failed:', error);
}
render() {
if (this.state.hasError) {
return <div>Failed to load component</div>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</ErrorBoundary>
);
}Error Boundaries catch errors thrown during rendering, including lazy component loading failures.
Create a simple test to verify the lazy component loads correctly:
import { lazy, Suspense } from 'react';
import { render, waitFor } from '@testing-library/react';
const MyComponent = lazy(() => import('./MyComponent'));
it('lazy loads the component', async () => {
const { getByText } = render(
<Suspense fallback={<div>Loading</div>}>
<MyComponent />
</Suspense>
);
// Wait for component to finish loading
await waitFor(() => {
expect(getByText('Expected content')).toBeInTheDocument();
});
});Testing in isolation helps identify whether the lazy load itself is the problem or if there's an issue with how the component renders.
Webpack Chunk Names: For better debugging, add webpack comments to name your chunks:
const MyComponent = lazy(() =>
import(/* webpackChunkName: "my-component" */ './MyComponent')
);This makes it easier to identify which chunk failed in build tools and production monitoring.
Route-Based Code Splitting: A common pattern is lazy-loading route components:
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading page...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}This reduces initial bundle size by splitting route components into separate chunks.
Preloading Lazy Components: Preload components before rendering to avoid loading delays:
const MyComponent = lazy(() => import('./MyComponent'));
// Preload before rendering
const preloadComponent = () => {
import('./MyComponent');
};
// Use on hover or link focus
<Link to="/page" onMouseEnter={preloadComponent}>
Go to page
</Link>This can significantly improve perceived performance.
Race Conditions: Be aware of potential race conditions when using lazy with dynamic imports and state:
function App() {
const [showComponent, setShowComponent] = useState(false);
// Component changes dynamically based on state
const Component = showComponent ? LazyA : LazyB;
return <Component />;
}Ensure your logic properly handles switching between different lazy components.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React