Load components and data with Suspense boundaries for better UX and streaming.
import { Suspense, lazy } from "react";
const HeavyComponent = lazy(() => import("./HeavyComponent"));
export default function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<HeavyComponent />
</Suspense>
);
}<Suspense fallback={<div>Loading page...</div>}>
<Header />
<MainContent />
<Sidebar />
</Suspense><Suspense fallback={<PageSkeleton />}>
<Dashboard />
</Suspense>const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
export default function App({ page }) {
return (
<Suspense fallback={<p>Loading page...</p>}>
{page === "dashboard" && <Dashboard />}
{page === "settings" && <Settings />}
</Suspense>
);
}const Home = lazy(() => import("./pages/Home"));
const Profile = lazy(() => import("./pages/Profile"));
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>function UserProfile({ userId }) {
const { data: user } = useSuspenseQuery({
queryKey: ["user", userId],
queryFn: () => fetchUser(userId)
});
return <div>{user.name}</div>;
}
<Suspense fallback={<p>Loading user...</p>}>
<UserProfile userId={1} />
</Suspense>// UserProfile suspends internally — wrap it here
function Page() {
return (
<Suspense fallback={<Spinner />}>
<UserProfile userId={userId} />
</Suspense>
);
}<Suspense fallback={<p>Loading page...</p>}>
<Header />
<Suspense fallback={<p>Loading content...</p>}>
<MainContent />
</Suspense>
<Suspense fallback={<p>Loading sidebar...</p>}>
<Sidebar />
</Suspense>
</Suspense>// Narrow boundary: only hides the part that's loading
function ProductList() {
return (
<ul>
{productIds.map(id => (
<Suspense key={id} fallback={<li>Loading...</li>}>
<ProductItem id={id} />
</Suspense>
))}
</ul>
);
}<ErrorBoundary fallback={<p>Error loading page</p>}>
<Suspense fallback={<p>Loading...</p>}>
<PageContent />
</Suspense>
</ErrorBoundary>function App() {
const [isPending, startTransition] = useTransition();
const [page, setPage] = useState("home");
const navigate = (newPage) => {
startTransition(() => setPage(newPage));
};
return (
<Suspense fallback={<p>Loading...</p>}>
{isPending && <p>Loading new page...</p>}
<PageContent page={page} />
</Suspense>
);
}// Preload on hover before user clicks
const LazyPage = lazy(() => import("./Page"));
function NavLink({ href, children }) {
return (
<a href={href} onMouseEnter={() => import("./Page")}>
{children}
</a>
);
}import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary fallbackRender={({ error }) => <p>Error: {error.message}</p>}>
<Suspense fallback={<Spinner />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>Suspense for data fetching is experimental in React 19 and not recommended for production without a supporting library. Use React Query or SWR instead — they integrate with Suspense via their own stable APIs and handle caching, retries, and deduplication.
Wrap React.lazy(() => import('./MyComponent')) in a Suspense boundary with a fallback prop: <Suspense fallback={
All siblings suspend together — the single fallback shows until every component in the boundary is ready. To show components progressively as they load, wrap each in its own Suspense boundary so resolved components render immediately without waiting for slower siblings.
Suspense handles the loading state but not errors — a thrown promise triggers the fallback, while a thrown error propagates up to the nearest error boundary. Always pair Suspense with an ErrorBoundary component wrapping it to catch fetch failures or import errors.
Yes — Next.js App Router uses React's streaming SSR with Suspense, sending HTML in chunks as each boundary resolves on the server. Wrap async Server Components in Suspense to stream partial content to the client rather than blocking the entire page render.