SalakCode SalakCode
Rendering

Server Components

Render React components on the server

Advanced
react nextjs ssr server-components

Definition

React Server Components (RSC) allow developers to render components exclusively on the server. They can access backend resources directly, reduce client-side JavaScript bundle size to zero, and seamlessly integrate with client components for interactivity.

How They Work

Server vs Client Components

// Server Component (default in App Router)
// - Render on server
// - Can access database/filesystem
// - Zero client JS
async function BlogPost({ slug }) {
  const post = await db.posts.findOne({ slug });
  
  return (
    <article>
      <h1>{post.title}</h1>
      <Markdown content={post.content} />
    </article>
  );
}

// Client Component
// - Render on client
// - Can use hooks, browser APIs
// - Interactivity
'use client';

import { useState } from 'react';

function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
  
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}

Benefits

Zero Bundle Size

// Server Component
import { format } from 'date-fns'; // 20kb library

function DateDisplay({ date }) {
  // This runs on server only
  // Client doesn't download date-fns
  return <time>{format(date, 'PPP')}</time>;
}

// Compare with Client Component
'use client';

import { format } from 'date-fns'; // Downloaded to client!

function DateDisplay({ date }) {
  return <time>{format(date, 'PPP')}</time>;
}

Direct Backend Access

// Server Component can query DB directly
import { db } from './db';

async function UserProfile({ userId }) {
  // No API endpoint needed!
  const user = await db.users.findUnique({
    where: { id: userId }
  });
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Automatic Code Splitting

// Client components imported in Server Components
// are automatically code-split

import { HeavyChart } from './HeavyChart'; // Lazy loaded!

async function AnalyticsPage() {
  const data = await fetchAnalytics();
  
  return (
    <div>
      <Summary data={data} />
      <HeavyChart data={data} /> {/* Loaded on demand */}
    </div>
  );
}

Patterns

Server Component Composition

// Layout is Server Component
export default function Layout({ children }) {
  return (
    <html>
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}

// Page is Server Component
export default async function Page() {
  const data = await fetchData();
  
  return (
    <main>
      <DataTable data={data} />
      <InteractiveFilter /> {/* Client Component */}
    </main>
  );
}

Passing Server Data to Client

async function ProductPage({ id }) {
  const product = await getProduct(id);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <AddToCartButton 
        productId={product.id} 
        price={product.price}
      />
    </div>
  );
}

'use client';

function AddToCartButton({ productId, price }) {
  const [count, setCount] = useState(1);
  
  return (
    <button onClick={() => addToCart(productId, count)}>
      Add {count} to cart (${price * count})
    </button>
  );
}

Streaming

// Components can suspend and stream
import { Suspense } from 'react';

export default function Dashboard() {
  return (
    <div>
      <Header />
      
      <Suspense fallback={<Skeleton />}>
        <SlowWidget />
      </Suspense>
      
      <Suspense fallback={<Skeleton />}>
        <AnotherSlowWidget />
      </Suspense>
    </div>
  );
}

async function SlowWidget() {
  const data = await fetchSlowData();
  return <Widget data={data} />;
}

Limitations

What Server Components Can’t Do

// ❌ Can't use hooks
function ServerComp() {
  const [state, setState] = useState(); // Error!
}

// ❌ Can't use browser APIs
function ServerComp() {
  localStorage.getItem('key'); // Error!
}

// ❌ Can't use event handlers
function ServerComp() {
  return <button onClick={handler}>Click</button>; // Error!
}

// ❌ Can't use Context
function ServerComp() {
  const value = useContext(MyContext); // Error!
}

Best Practices

Move Client Code Down

// Bad: Whole page is client component
'use client';

function Page() {
  const [filter, setFilter] = useState('');
  
  return (
    <div>
      <Header />
      <input value={filter} onChange={...} />
      <DataList filter={filter} />
    </div>
  );
}

// Good: Only interactive part is client
async function Page() {
  const data = await fetchData();
  
  return (
    <div>
      <Header />
      <FilterableList data={data} />
    </div>
  );
}

'use client';

function FilterableList({ data }) {
  const [filter, setFilter] = useState('');
  // Only this component in client bundle
  return (...);
}

Data Fetching

// Fetch close to where data is used
async function Post({ id }) {
  const post = await fetch(`https://api.example.com/posts/${id}`);
  // Component suspends here
  
  return <article>{post.content}</article>;
}

// Parallel fetching
async function Page() {
  // Start both requests immediately
  const userPromise = getUser();
  const postsPromise = getPosts();
  
  // Wait for both
  const [user, posts] = await Promise.all([
    userPromise,
    postsPromise
  ]);
  
  return (...);
}
Key Takeaway

Server Components render exclusively on the server, reducing bundle size and enabling direct backend access. They complement Client Components - use Server Components for data fetching and static content, Client Components for interactivity. This hybrid approach provides the best of both worlds: fast initial loads and rich user interactions.

Resources

Related Topics