React throws this error when a `<Suspense>` boundary never catches a Promise or an async child, so the runtime refuses to render the boundary or its fallback.
Fixes Cannot use Suspense boundary without an async component
# Cannot use Suspense boundary without an async componentCannot use Suspense boundary without an async component
On this page
A `<Suspense>` boundary only makes sense when one of its descendants can suspend, which happens when a component either throws a `Promise` (such as an `async` server component that `await`s data or a `React.lazy` wrapper) or is streaming data. When the tree stays fully synchronous, React cannot show a fallback and therefore throws this intent-checking error to signal that the boundary is unnecessary or misconfigured. This safeguard was introduced as part of the concurrent/Server Component workflow so that React can reason about when to pause and resume rendering.
The mistake is almost always right next to the <code><Suspense></code> declaration. Look at the file listed in the stack trace and focus on the direct child of the boundary. If the child is a plain component like the one below, nothing inside the boundary suspends and React refuses to let it exist:
<pre><code class="language-tsx">// layout.tsx
import { Suspense } from 'react';
export default function Layout() {
return (
<Suspense fallback={<Loading />}>
<Hero /> // regular, synchronous server component
</Suspense>
);
}
</code></pre>
React throws the error before it tries to render Loading because it sees a Suspense boundary with only synchronous content.
Convert the component inside <code><Suspense></code> into an <code>async</code> server component that actually suspends by awaiting data or resources. For example:
<pre><code class="language-tsx">// components/Hero.tsx (server)
const Hero = async () => {
const res = await fetch('https://api.example.com/hero', { cache: 'no-store' });
const hero = await res.json();
return <section>{hero.title}</section>;
};
// layout.tsx
<Suspense fallback={<Loading />}>
<Hero />
</Suspense>
</code></pre>
await fetch causes React to suspend, satisfies the boundary, and React now streams the fallback until the promise resolves.
If the boundary wraps a client component, make that component asynchronous with React.lazy so the tree can suspend while the chunk loads:
<pre><code class="language-tsx">const Chart = React.lazy(() => import('./Chart'));
function Dashboard() {
return (
<Suspense fallback={<LoadingChart />}>
<Chart />
</Suspense>
);
}
</code></pre>
The promise that React.lazy returns keeps the boundary honest, and React stops throwing the error.
If there is no asynchronous work left, the easiest fix is to remove <code><Suspense></code> entirely. Keeping an unused boundary only invites this error:
<pre><code class="language-tsx">// remove Suspense when everything is synchronous
export default function Layout() {
return <Hero />; // no boundary needed when Hero renders right away
}
</code></pre>
Only wrap the tree with <code><Suspense></code> when you know it suspends (via await, data fetching helpers, or React.lazy).
Every Suspense boundary must still describe a fallback UI even if it eventually renders instantly. It is safe to add a boundary above a chunk that may or may not suspend as long as at least one descendant actually throws a promise. This is why server architects often place <Suspense> around the portion of the tree that calls await use or await fetch. React validates that requirement early to avoid streaming meaningless fallbacks or confusing builds when using server components.