Best practices for using Suspense in production
Suspense is powerful but must be used carefully to provide a smooth and professional experience in real-world applications.
Here are the best practices you should follow when using Suspense in production:
1. Always Provide a Meaningful fallback
Don't just show "Loading..." for everything.
Tailor the fallback to match user expectations:
- For major screens ➔ use skeleton UIs or full-page loaders.
- For small widgets ➔ use spinners or tiny placeholders.
Example:
<Suspense fallback={<SkeletonLoader />}> <UserProfile /> </Suspense>
A good fallback reduces perceived loading time and keeps users engaged.
2. Use Multiple Suspense Boundaries (Granular Loading)
Instead of wrapping your entire app in one <Suspense>
, split it into sections.
✅ Good:
<Suspense fallback={<SidebarLoader />}> <Sidebar /> </Suspense> <Suspense fallback={<ContentLoader />}> <MainContent /> </Suspense>
This way:
- Different parts of the page can load independently.
- Some content appears faster, improving UX.
- Loading feels dynamic and responsive.
3. Avoid "Suspense waterfalls"
Waterfalls happen when multiple Suspense components load one after another instead of together, making the app feel slower.
🧠 Solution: Preload or batch loading when possible, especially for related components.
Example with React.lazy
:
const ComponentA = lazy(() => import('./ComponentA')); const ComponentB = lazy(() => import('./ComponentB'));
If you know both are needed, you can start preloading them earlier.
Some libraries and frameworks (like Next.js) optimize this for you automatically.
4. Combine Suspense with Data Fetching Libraries
Use libraries that natively support Suspense for better experience:
- React Query ➔
useSuspenseQuery
- Relay ➔ built-in Suspense support
- SWR ➔ Suspense mode
Example (React Query):
const { data } = useSuspenseQuery(['user', id], fetchUser); return <div>Hello {data.name}</div>;
This removes a lot of manual loading state management from your code.
5. Always Test Suspense Carefully
Because loading states are dynamic, always test:
- Slow networks (simulate "slow 3G" in Chrome dev tools)
- Error states (what if data fails to load?)
- Fallback timings (how long does the fallback show?)
Production users expect instant feedback — even if actual content takes time.
Bonus Tip:
If you're using Suspense for data fetching in production, consider Error Boundaries alongside Suspense to gracefully handle errors without crashing the entire app.
Example:
<ErrorBoundary fallback={<ErrorScreen />}> <Suspense fallback={<LoadingScreen />}> <Dashboard /> </Suspense> </ErrorBoundary>