How to fix Cannot use Suspense boundary without an async component in React
This error occurs when you wrap components in a Suspense boundary but none of the child components actually suspend (throw a Promise). Suspense requires at least one async operation like lazy loading, data fetching with a Suspense-compatible library, or the use() hook in React 19. Understanding how Suspense works and when components properly suspend resolves this issue.
Quick fix
Fixes Cannot use Suspense boundary without an async component
import { Suspense, lazy } from "react";
// Lazy load the component - this makes it suspend
const HeavyComponent = lazy(() => import("./HeavyComponent"));
export function App() {
return (
<Suspense fallback={<div>Loading component...</div>}>
<HeavyComponent />
</Suspense>
);
}- Use React.lazy() for code splitting to enable Suspense
- Use React 19's use() hook for data fetching
- Use Suspense-compatible data fetching libraries
- Combine Suspense with Error Boundaries
import { Suspense, lazy } from "react";// Lazy load the component - this makes it suspendconst HeavyComponent = lazy(() => import("./HeavyComponent"));export function App() {return (<Suspense fallback={<div>Loading component...</div>}><HeavyComponent /></Suspense>);}Cannot use Suspense boundary without an async component
What this error means
React Suspense is designed to handle asynchronous operations by displaying a fallback UI while waiting for content to load. However, Suspense only activates when a child component "suspends" by throwing a Promise during rendering. If you wrap regular synchronous components in a Suspense boundary without any actual async operations, React may warn or fail because there is nothing to suspend. The error indicates that your Suspense boundary contains only components that render synchronously without triggering the suspension mechanism. Suspense is not just a loading state wrapper—it requires specific integration with async patterns like React.lazy(), data-fetching libraries that support Suspense, or React 19's use() hook.
Typical symptoms
- Error message stating "Cannot use Suspense boundary without an async component"
- Suspense fallback never appears even though it is defined
- Components render immediately without showing loading states
- Console warnings about improper Suspense usage
- Suspense boundary acting as if it is not present
- Loading indicators not displaying when expected during data fetching
- React development mode warnings about Suspense boundaries with no suspending children
Common causes
- Wrapping regular synchronous components in Suspense without any lazy loading or async operations
- Using data-fetching methods that do not support Suspense (like traditional useEffect with fetch)
- Forgetting to import components with React.lazy() when attempting code splitting
- Using Suspense with libraries that do not implement the Suspense protocol (older versions of React Query, SWR without proper configuration)
- Attempting to use Suspense for data fetching in React 18 without using frameworks like Next.js or libraries that support Suspense
- Not wrapping async components with Error Boundaries, causing errors to break the Suspense flow
- Using async/await syntax in components without properly throwing Promises for Suspense to catch
- Misunderstanding that Suspense requires components to throw Promises, not just be async functions
How to fix "Cannot use Suspense boundary without an async component"
The most common and straightforward use of Suspense is with React.lazy() for code splitting. Lazy-loaded components automatically suspend while their code is being fetched. Wrap the lazy component in a Suspense boundary with a fallback.
import { Suspense, lazy } from "react";
// Lazy load the component - this makes it suspend
const HeavyComponent = lazy(() => import("./HeavyComponent"));
export function App() {
return (
<Suspense fallback={<div>Loading component...</div>}>
<HeavyComponent />
</Suspense>
);
}React 19 introduced the use() hook which allows you to read Promises directly in render. When you call use() with a Promise, the component suspends until the Promise resolves. This is the official way to integrate Suspense with async data.
import { Suspense, use } from "react";
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
// Calling use() suspends the component until userPromise resolves
const user = use(userPromise);
return <div>Welcome, {user.name}</div>;
}
export function App() {
const userPromise = fetchUser();
return (
<Suspense fallback={<div>Loading user...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}Note: The use() hook is only available in React 19+.
Libraries like React Query, SWR, and Relay support Suspense mode. Enable Suspense support in their configuration, and they will automatically throw Promises when data is loading, triggering your Suspense fallback.
import { Suspense } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
function UserData() {
// useSuspenseQuery suspends while fetching
const { data } = useSuspenseQuery({
queryKey: ["user"],
queryFn: fetchUser,
});
return <div>{data.name}</div>;
}
export function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserData />
</Suspense>
);
}For SWR:
import useSWR from "swr";
function UserData() {
const { data } = useSWR("/api/user", fetcher, { suspense: true });
return <div>{data.name}</div>;
}Suspense only handles loading states. To handle errors in async components, wrap your Suspense boundary with an Error Boundary. This ensures errors during loading do not break your app.
import { Suspense, Component } from "react";
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong</div>;
}
return this.props.children;
}
}
export function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
);
}Or use a library like react-error-boundary:
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary fallback={<div>Error occurred</div>}>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>Traditional data fetching with useEffect and useState does not trigger Suspense. If you are using this pattern, either migrate to a Suspense-compatible library or remove the Suspense boundary and handle loading states manually.
// WRONG - This does NOT suspend
function UserData() {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, []);
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
// Wrapping this in Suspense does nothing - remove Suspense or migrate to use()Ensure the libraries you use actually support Suspense. Older versions of React Query, SWR, Apollo Client, and other data-fetching libraries may not support Suspense mode.
For React Query, use @tanstack/react-query v4+ and useSuspenseQuery.
For SWR, enable { suspense: true } in configuration.
For Apollo Client, use the @apollo/client v3.8+ with Suspense hooks.
Check library documentation for the correct Suspense integration method.
In Next.js 13+ with App Router, server components can use async/await directly and integrate seamlessly with Suspense. This is the recommended approach for server-side data fetching with Suspense.
// app/page.tsx (Next.js App Router)
import { Suspense } from "react";
async function UserData() {
const user = await fetchUser();
return <div>{user.name}</div>;
}
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserData />
</Suspense>
);
}Server components that use async/await automatically suspend when used with Suspense.
Advanced notes
Understanding how Suspense works internally helps debug issues. Suspense listens for components that throw Promises during rendering. When a Promise is thrown, React catches it, renders the fallback UI, and waits for the Promise to resolve. Once resolved, React re-renders the component. This "throw and catch" mechanism is why regular async/await functions do not work—they return Promises instead of throwing them. The use() hook in React 19 bridges this gap by handling the Promise throw internally. For React 18 and earlier, you must use libraries that implement this pattern or use React.lazy(). In production applications, consider using frameworks like Next.js that provide first-class Suspense support with Server Components and streaming SSR. Be aware that Suspense is still evolving, and some edge cases with nested Suspense boundaries, concurrent updates, and transitions may behave unexpectedly. Always test Suspense behavior in production builds, as development mode may behave differently. For fine-grained control over loading states, consider using the useTransition() hook alongside Suspense to prevent UI "jank" when navigating between Suspense boundaries.
Frequently asked questions
- What does "Cannot use Suspense boundary without an async component" mean?
- React Suspense is designed to handle asynchronous operations by displaying a fallback UI while waiting for content to load. However, Suspense only activates when a child component "suspends" by throwing a Promise during rendering. If you wrap regular synchronous components in a Suspense boundary without any actual async operations, React may warn or fail because there is nothing to suspend.
- How do I fix "Cannot use Suspense boundary without an async component"?
- This error occurs when you wrap components in a Suspense boundary but none of the child components actually suspend (throw a Promise). Suspense requires at least one async operation like lazy loading, data fetching with a Suspense-compatible library, or the use() hook in React 19. Understanding how Suspense works and when components properly suspend resolves this issue.
- What causes "Cannot use Suspense boundary without an async component"?
- Wrapping regular synchronous components in Suspense without any lazy loading or async operations Using data-fetching methods that do not support Suspense (like traditional useEffect with fetch) Forgetting to import components with React.lazy() when attempting code splitting
- Is the "Cannot use Suspense boundary without an async component" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚React Official Documentation
- 📖React Suspense official documentation(Official Docs)
- 📖React 19 release announcement with use() hook(Official Docs)
- 📝
- 📝