Mastering React Server Components and Suspense in Next.js 15
Why React Server Components (RSC)?
RSC moves rendering and data-fetching to the server by default, sending lightweight component payloads to the client. This reduces bundle size, improves Time-to-Interactive, and enables streaming UI.
Mental Model: Server vs Client Components
Server Components: run on the server, can access DB/SDKs safely, no client JS by default.
- Client Components: run in the browser, can use state, effects, event handlers.
- Use "use client" at the top of files that need client features.
Data Fetching with async Components
export default async function Page() {
const data = await getProducts(); // server-side fetch
return <ProductList data={data} />; // zero JS on client
}
Streaming UI with Suspense
Wrap slow subtrees with <Suspense> to stream the rest of the page immediately. Pair with route-level or segment loading.js for graceful fallbacks.
import { Suspense } from 'react';
import Loading from '@/app/components/Loading';
export default function Page() {
return (
<>
<Hero />
<Suspense fallback={<Loading />}>
<SlowRecommendations />
</Suspense>
</>
);
}
Caching and Revalidation
Use fetch(..., { cache: 'force-cache' | 'no-store', next: { revalidate: 60 } }) to control caching. RSC integrates with the Next.js Cache and React cache() for deduplication.
Interop: Using Client Components
Lift server data into client components via props. Avoid importing server-only modules (DB clients) in client components.
Common Pitfalls
- Forgetting "use client" where event handlers are used
- Leaking server secrets into client bundles
- Overusing client components and losing RSC benefits
- Not wrapping useSearchParams / async segments with Suspense
Takeaways
Default to Server Components, isolate interactivity into small client islands, and leverage Suspense + streaming to keep pages fast.