Suspense cheatsheet.
What Suspense does
Pauses rendering until something is ready. Shows fallback while paused. Reveals content when ready.
<Suspense fallback={<Spinner />}>
<UserPage />
</Suspense>
If UserPage (or anything inside) “suspends,” the fallback renders.
What suspends
React.lazy()imports- The
use()hook with a pending Promise/Context - Server Components fetching data (in RSC frameworks)
- TanStack Query / Relay / SWR with suspense mode
Code splitting
const Settings = React.lazy(() => import("./Settings"));
<Suspense fallback={<Loader />}>
<Settings />
</Suspense>
Browser loads the chunk on demand.
use(Promise)
function User({ promise }: { promise: Promise<User> }) {
const user = use(promise);
return <p>{user.name}</p>;
}
const promise = fetchUser(id);
<Suspense fallback={<Loader />}>
<User promise={promise} />
</Suspense>
use(promise) suspends until it resolves.
Promise from above
Don’t create promises during render — they get re-created each render.
// BAD: new promise per render
function Wrapper({ id }: { id: number }) {
const p = fetchUser(id);
return <User promise={p} />;
}
// GOOD: lift / memoize
const promise = useMemo(() => fetchUser(id), [id]);
In RSC, fetch is done in the server component and passed down.
Nested boundaries
<Suspense fallback={<PageLoader />}>
<Header />
<Suspense fallback={<UserSkeleton />}>
<User />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<Feed />
</Suspense>
</Suspense>
Header shows immediately. User and Feed show their own skeletons until ready.
Error boundaries
Suspense handles “loading.” Errors need ErrorBoundary:
class ErrorBoundary extends React.Component {
state = { error: null };
static getDerivedStateFromError(error) { return { error }; }
render() {
if (this.state.error) return this.props.fallback(this.state.error);
return this.props.children;
}
}
<ErrorBoundary fallback={(e) => <p>error: {e.message}</p>}>
<Suspense fallback={<Loader />}>
<User />
</Suspense>
</ErrorBoundary>
Use react-error-boundary:
npm i react-error-boundary
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary FallbackComponent={({ error }) => <p>{error.message}</p>}>
<Suspense fallback={<L />}>
<User />
</Suspense>
</ErrorBoundary>
SuspenseList (experimental)
<SuspenseList revealOrder="forwards">
<Suspense fallback={<L1 />}><A /></Suspense>
<Suspense fallback={<L2 />}><B /></Suspense>
<Suspense fallback={<L3 />}><C /></Suspense>
</SuspenseList>
Reveal order: forwards / backwards / together.
Streaming SSR
Server starts streaming HTML before all data is ready:
<Suspense fallback={<HeaderSkel />}>
<Header /> // fast
</Suspense>
<Suspense fallback={<FeedSkel />}>
<Feed /> // slow
</Suspense>
Server flushes Header HTML immediately, streams Feed when ready. Faster TTFB and FCP.
Next.js, Remix, and Astro all support this.
TanStack Query suspense
const { data } = useSuspenseQuery({
queryKey: ["user", id],
queryFn: () => fetchUser(id),
});
Suspends until data is ready. Always returns data (non-nullable).
Transitions + suspense
const [isPending, startTransition] = useTransition();
function nav(href) {
startTransition(() => {
setRoute(href); // new page suspends
});
}
While transitioning, current UI stays visible (no fallback flash). isPending indicates load.
Fallback design
- Skeleton screens > spinners (less jarring).
- Match dimensions of real content to avoid layout shift.
- One spinner per logical unit, not nested everywhere.
Avoiding cascades
// BAD: nested suspenses cause sequential loads
<Suspense fallback={<L1 />}>
<Parent> // suspends on data
<Suspense fallback={<L2 />}>
<Child /> // suspends on data only after parent loads
</Suspense>
</Parent>
</Suspense>
// BETTER: parallel
const parent = fetchParent();
const child = fetchChild();
In RSC frameworks, kick off requests in parallel.
Common mistakes
- Promise re-created per render — infinite suspend.
- ErrorBoundary outside Suspense — error doesn’t get caught from suspension.
- Layout shift between fallback and content.
- Top-level Suspense only — all-or-nothing loading.
use(promise)without wrapping Suspense — error.
Read this next
If you want my Suspense + ErrorBoundary patterns, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .