Back to Blog
ReactServer ComponentsNext.jsPerformance

React Server Components: A Practical Guide

Understand React Server Components. Learn when to use them, data fetching patterns, and migration strategies.

B
Bootspring Team
Engineering
February 27, 2026
3 min read

Server Components render on the server with zero client JavaScript. Here's how to use them effectively.

Server vs Client Components#

1// Server Component (default in App Router) 2// Can access database, file system, environment variables 3async function UserProfile({ userId }: { userId: string }) { 4 const user = await db.users.findUnique({ where: { id: userId } }); 5 6 return ( 7 <div> 8 <h1>{user.name}</h1> 9 <p>{user.bio}</p> 10 </div> 11 ); 12} 13 14// Client Component - add 'use client' directive 15'use client'; 16 17import { useState } from 'react'; 18 19function Counter() { 20 const [count, setCount] = useState(0); 21 22 return ( 23 <button onClick={() => setCount(c => c + 1)}> 24 Count: {count} 25 </button> 26 ); 27}

When to Use Each#

Server ComponentsClient Components
Fetch dataEvent handlers (onClick, onChange)
Access backend resourcesuseState, useEffect
Keep sensitive info on serverBrowser APIs
Large dependenciesInteractivity

Data Fetching Patterns#

1// Parallel data fetching 2async function Dashboard() { 3 // These fetch in parallel 4 const [user, posts, analytics] = await Promise.all([ 5 getUser(), 6 getPosts(), 7 getAnalytics(), 8 ]); 9 10 return ( 11 <div> 12 <UserCard user={user} /> 13 <PostList posts={posts} /> 14 <AnalyticsChart data={analytics} /> 15 </div> 16 ); 17} 18 19// Sequential when needed 20async function PostPage({ postId }: { postId: string }) { 21 const post = await getPost(postId); 22 const comments = await getComments(post.id); // Needs post.id 23 24 return <Post post={post} comments={comments} />; 25}

Mixing Server and Client#

1// Server Component 2async function ProductPage({ id }: { id: string }) { 3 const product = await getProduct(id); 4 5 return ( 6 <div> 7 <h1>{product.name}</h1> 8 <p>{product.description}</p> 9 {/* Client Component for interactivity */} 10 <AddToCartButton productId={id} price={product.price} /> 11 </div> 12 ); 13} 14 15// Client Component 16'use client'; 17 18function AddToCartButton({ productId, price }: Props) { 19 const [adding, setAdding] = useState(false); 20 21 const handleAdd = async () => { 22 setAdding(true); 23 await addToCart(productId); 24 setAdding(false); 25 }; 26 27 return ( 28 <button onClick={handleAdd} disabled={adding}> 29 Add to Cart - ${price} 30 </button> 31 ); 32}

Passing Server Data to Client#

1// ✅ Pass serializable props 2async function Page() { 3 const data = await fetchData(); 4 return <ClientComponent data={data} />; 5} 6 7// ❌ Can't pass functions or classes 8async function Page() { 9 const handler = () => console.log('click'); 10 return <ClientComponent onClick={handler} />; // Error! 11} 12 13// ✅ Use Server Actions for mutations 14async function Page() { 15 async function submitForm(formData: FormData) { 16 'use server'; 17 await saveToDatabase(formData); 18 } 19 20 return <ClientForm action={submitForm} />; 21}

Streaming with Suspense#

1import { Suspense } from 'react'; 2 3async function Page() { 4 return ( 5 <div> 6 <Header /> {/* Renders immediately */} 7 8 <Suspense fallback={<ProductSkeleton />}> 9 <ProductList /> {/* Streams when ready */} 10 </Suspense> 11 12 <Suspense fallback={<ReviewsSkeleton />}> 13 <Reviews /> {/* Streams independently */} 14 </Suspense> 15 </div> 16 ); 17}

Best Practices#

  1. Start with Server Components - Only add 'use client' when needed
  2. Push client boundaries down - Keep interactive parts small
  3. Fetch data at the top - Pass down as props
  4. Use Suspense for loading states - Better UX than full page loading

Server Components reduce bundle size, improve performance, and simplify data fetching.

Share this article

Help spread the word about Bootspring