We use cookies to enhance your experience on the site
CodeWorlds

React Server Components (RSC) - The Future of React

React Server Components are a revolutionary React feature that allows rendering components on the server side while maintaining interactivity on the client side. This is a fundamental change in how we think about React applications.

What Are React Server Components?

React Server Components are components that are rendered on the server side and send ready HTML to the client along with instructions for React on how to integrate that HTML with interactive client components.

Differences Between RSC and SSR

1// Traditional SSR - entire application rendered on server
2function TraditionalSSRPage() {
3  const [count, setCount] = useState(0);
4  const posts = await fetchPosts(); // All logic must be on client after hydration
5
6  return (
7    <div>
8      <PostsList posts={posts} />
9      <Counter count={count} setCount={setCount} />
10    </div>
11  );
12}
13
14// React Server Components - server and client mix
15async function ServerComponentPage() {
16  // This code runs ONLY on the server
17  const posts = await fetchPosts(); // Direct database call
18  const user = await getUser();
19
20  return (
21    <div>
22      {/* Server Component - rendered on server */}
23      <PostsList posts={posts} />
24
25      {/* Client Component - interactive on client */}
26      <Counter />
27
28      {/* Server Component with server data */}
29      <UserProfile user={user} />
30    </div>
31  );
32}

Server Components Basics

Server vs Client Components

1// ServerComponent.jsx (server component by default)
2import { db } from './database';
3
4export default async function PostsList() {
5  // Direct database access
6  const posts = await db.posts.findMany({
7    include: { author: true },
8    orderBy: { createdAt: 'desc' }
9  });
10
11  return (
12    <div className="posts-list">
13      {posts.map(post => (
14        <article key={post.id}>
15          <h2>{post.title}</h2>
16          <p>By {post.author.name}</p>
17          <p>{post.excerpt}</p>
18
19          {/* Client Component for interaction */}
20          <LikeButton postId={post.id} initialLikes={post.likes} />
21        </article>
22      ))}
23    </div>
24  );
25}
26
27// LikeButton.jsx (client component)
28'use client'; // Directive marking a client component
29
30import { useState } from 'react';
31
32export default function LikeButton({ postId, initialLikes }) {
33  const [likes, setLikes] = useState(initialLikes);
34  const [isLiking, setIsLiking] = useState(false);
35
36  const handleLike = async () => {
37    setIsLiking(true);
38    try {
39      const response = await fetch(`/api/posts/${postId}/like`, {
40        method: 'POST'
41      });
42      const data = await response.json();
43      setLikes(data.likes);
44    } catch (error) {
45      console.error('Failed to like post:', error);
46    } finally {
47      setIsLiking(false);
48    }
49  };
50
51  return (
52    <button
53      onClick={handleLike}
54      disabled={isLiking}
55      className="like-button"
56    >
57      ❤️ {likes} {isLiking && '...'}
58    </button>
59  );
60}

Data Fetching in Server Components

1// utils/api.js (server-side only)
2import { cache } from 'react';
3
4// Automatic caching for the same request
5export const getPosts = cache(async () => {
6  const response = await fetch('https://api.example.com/posts', {
7    // Server Components can safely use secrets
8    headers: {
9      'Authorization': `Bearer ${process.env.API_SECRET}`
10    }
11  });
12  return response.json();
13});
14
15export const getUser = cache(async (id) => {
16  // Direct database query
17  return await db.user.findUnique({
18    where: { id },
19    include: { profile: true }
20  });
21});
22
23// components/PostsPage.jsx
24import { getPosts, getUser } from '../utils/api';
25import { Suspense } from 'react';
26
27export default async function PostsPage({ userId }) {
28  // Parallel data fetching
29  const [posts, user] = await Promise.all([
30    getPosts(),
31    getUser(userId)
32  ]);
33
34  return (
35    <main>
36      <h1>Welcome, {user.name}!</h1>
37
38      {/* Nested Server Component with its own data fetching */}
39      <Suspense fallback={<div>Loading posts...</div>}>
40        <PostsList posts={posts} />
41      </Suspense>
42
43      {/* Client Component for interaction */}
44      <CreatePostForm userId={userId} />
45    </main>
46  );
47}

Streaming and Suspense

Progressive Content Loading

1// components/DashboardPage.jsx
2import { Suspense } from 'react';
3
4export default function DashboardPage() {
5  return (
6    <div className="dashboard">
7      <h1>Dashboard</h1>
8
9      {/* Fast loading content */}
10      <Suspense fallback={<SkeletonCard />}>
11        <UserStats />
12      </Suspense>
13
14      {/* Slower loading content */}
15      <Suspense fallback={<SkeletonChart />}>
16        <AnalyticsChart />
17      </Suspense>
18
19      {/* Very slow loading content */}
20      <Suspense fallback={<SkeletonTable />}>
21        <ReportsTable />
22      </Suspense>
23    </div>
24  );
25}
26
27// components/UserStats.jsx (Server Component)
28async function UserStats() {
29  // Fast query - cache hit
30  const stats = await getUserStats();
31
32  return (
33    <div className="stats-grid">
34      <StatCard title="Total Users" value={stats.totalUsers} />
35      <StatCard title="Active Today" value={stats.activeToday} />
36      <StatCard title="Revenue" value={`$${stats.revenue}`} />
37    </div>
38  );
39}
40
41// components/AnalyticsChart.jsx (Server Component)
42async function AnalyticsChart() {
43  // Medium-length query
44  const chartData = await getAnalyticsData();
45
46  return (
47    <div className="chart-container">
48      {/* Client Component for interactive chart */}
49      <InteractiveChart data={chartData} />
50    </div>
51  );
52}
53
54// components/ReportsTable.jsx (Server Component)
55async function ReportsTable() {
56  // Long query - complex aggregations
57  const reports = await getComplexReports();
58
59  return (
60    <div className="reports-table">
61      <table>
62        <thead>
63          <tr>
64            <th>Report</th>
65            <th>Status</th>
66            <th>Date</th>
67          </tr>
68        </thead>
69        <tbody>
70          {reports.map(report => (
71            <tr key={report.id}>
72              <td>{report.name}</td>
73              <td>{report.status}</td>
74              <td>{report.date}</td>
75            </tr>
76          ))}
77        </tbody>
78      </table>
79    </div>
80  );
81}

Server and Client Component Composition

Patterns for Collaboration

1// patterns/CompositionPatterns.jsx
2
3// GOOD - Server Component can render Client Component
4function ServerParent() {
5  const data = await fetchData();
6
7  return (
8    <div>
9      <h1>Server Rendered Title</h1>
10      <ClientChild data={data} />
11    </div>
12  );
13}
14
15// GOOD - Client Component can render Server Component via children
16'use client';
17function ClientWrapper({ children }) {
18  const [isOpen, setIsOpen] = useState(false);
19
20  return (
21    <div>
22      <button onClick={() => setIsOpen(!isOpen)}>
23        Toggle Content
24      </button>
25      {isOpen && children}
26    </div>
27  );
28}
29
30// Usage:
31function ParentPage() {
32  return (
33    <ClientWrapper>
34      <ServerContent /> {/* Server Component as children */}
35    </ClientWrapper>
36  );
37}
38
39// BAD - Client Component cannot directly import Server Component
40'use client';
41function BadClientComponent() {
42  const [show, setShow] = useState(false);
43
44  return (
45    <div>
46      {show && <ServerContent />} {/* This won't work! */}
47    </div>
48  );
49}
50
51// GOOD - Using render props pattern
52'use client';
53function GoodClientComponent({ renderContent }) {
54  const [show, setShow] = useState(false);
55
56  return (
57    <div>
58      <button onClick={() => setShow(!show)}>Toggle</button>
59      {show && renderContent()}
60    </div>
61  );
62}
63
64// Usage:
65function ParentComponent() {
66  return (
67    <GoodClientComponent
68      renderContent={() => <ServerContent />}
69    />
70  );
71}

Advanced Patterns

Data Mutations in RSC

1// actions/postActions.js
2'use server'; // Server Actions
3
4import { revalidatePath } from 'next/cache';
5
6export async function createPost(formData) {
7  const title = formData.get('title');
8  const content = formData.get('content');
9
10  // Server-side validation
11  if (!title || !content) {
12    return { error: 'Title and content are required' };
13  }
14
15  try {
16    const post = await db.post.create({
17      data: { title, content, userId: getCurrentUserId() }
18    });
19
20    // Revalidate cache for the posts page
21    revalidatePath('/posts');
22
23    return { success: true, post };
24  } catch (error) {
25    return { error: 'Failed to create post' };
26  }
27}
28
29export async function deletePost(postId) {
30  try {
31    await db.post.delete({
32      where: { id: postId }
33    });
34
35    revalidatePath('/posts');
36    return { success: true };
37  } catch (error) {
38    return { error: 'Failed to delete post' };
39  }
40}
41
42// components/CreatePostForm.jsx
43'use client';
44
45import { createPost } from '../actions/postActions';
46import { useFormStatus } from 'react-dom';
47
48function SubmitButton() {
49  const { pending } = useFormStatus();
50
51  return (
52    <button type="submit" disabled={pending}>
53      {pending ? 'Creating...' : 'Create Post'}
54    </button>
55  );
56}
57
58export default function CreatePostForm() {
59  const [state, formAction] = useActionState(createPost, null);
60
61  return (
62    <form action={formAction}>
63      <div>
64        <label htmlFor="title">Title:</label>
65        <input
66          type="text"
67          id="title"
68          name="title"
69          required
70        />
71      </div>
72
73      <div>
74        <label htmlFor="content">Content:</label>
75        <textarea
76          id="content"
77          name="content"
78          required
79        />
80      </div>
81
82      {state?.error && (
83        <div className="error">{state.error}</div>
84      )}
85
86      {state?.success && (
87        <div className="success">Post created successfully!</div>
88      )}
89
90      <SubmitButton />
91    </form>
92  );
93}

Cache Strategies

1// utils/cache.js
2import { unstable_cache } from 'next/cache';
3
4// Custom cache wrapper with tags
5export function createCachedFunction(fn, keyParts, options = {}) {
6  return unstable_cache(
7    fn,
8    keyParts,
9    {
10      revalidate: 3600, // 1 hour default
11      tags: options.tags || [],
12      ...options
13    }
14  );
15}
16
17// Cached functions
18export const getCachedPosts = createCachedFunction(
19  async () => {
20    return await db.post.findMany({
21      include: { author: true },
22      orderBy: { createdAt: 'desc' }
23    });
24  },
25  ['posts'],
26  {
27    tags: ['posts'],
28    revalidate: 300 // 5 minutes
29  }
30);
31
32export const getCachedUser = createCachedFunction(
33  async (userId) => {
34    return await db.user.findUnique({
35      where: { id: userId },
36      include: { profile: true }
37    });
38  },
39  ['user'],
40  {
41    tags: ['users'],
42    revalidate: 3600 // 1 hour
43  }
44);
45
46// components/PostsWithCache.jsx
47export default async function PostsWithCache() {
48  // Automatic caching
49  const posts = await getCachedPosts();
50
51  return (
52    <div>
53      <h1>Cached Posts</h1>
54      {posts.map(post => (
55        <article key={post.id}>
56          <h2>{post.title}</h2>
57          <p>By {post.author.name}</p>
58          <CacheRevalidateButton postId={post.id} />
59        </article>
60      ))}
61    </div>
62  );
63}
64
65// actions/cacheActions.js
66'use server';
67
68import { revalidateTag } from 'next/cache';
69
70export async function revalidatePosts() {
71  revalidateTag('posts');
72}
73
74export async function revalidateUsers() {
75  revalidateTag('users');
76}

Performance and Optimizations

Automatic Bundle Splitting

1// Server Components don't end up in the client bundle!
2
3// ServerHeavyComponent.jsx (Server Component)
4import { someHeavyLibrary } from 'heavy-library'; // Won't be in client bundle!
5import { complexCalculation } from './utils/complex';
6
7export default async function ServerHeavyComponent() {
8  const data = await fetchData();
9  const processed = complexCalculation(data); // Happens on server
10  const result = someHeavyLibrary.process(processed);
11
12  return (
13    <div>
14      <h2>Processed Data</h2>
15      <pre>{JSON.stringify(result, null, 2)}</pre>
16    </div>
17  );
18}
19
20// ClientLightComponent.jsx (Client Component)
21'use client';
22
23import { useState } from 'react';
24
25export default function ClientLightComponent({ serverData }) {
26  const [selected, setSelected] = useState(null);
27
28  return (
29    <div>
30      {serverData.map(item => (
31        <button
32          key={item.id}
33          onClick={() => setSelected(item)}
34          className={selected?.id === item.id ? 'selected' : ''}
35        >
36          {item.name}
37        </button>
38      ))}
39      {selected && <ItemDetails item={selected} />}
40    </div>
41  );
42}

Error Boundaries for Server Components

1// components/ServerErrorBoundary.jsx
2'use client';
3
4import { Component } from 'react';
5
6export class ServerErrorBoundary extends Component {
7  constructor(props) {
8    super(props);
9    this.state = { hasError: false, error: null };
10  }
11
12  static getDerivedStateFromError(error) {
13    return { hasError: true, error };
14  }
15
16  componentDidCatch(error, errorInfo) {
17    console.error('Server component error:', error, errorInfo);
18    // Send to monitoring service
19  }
20
21  render() {
22    if (this.state.hasError) {
23      return (
24        <div className="error-boundary">
25          <h2>Something went wrong on the server</h2>
26          <details>
27            <summary>Error details</summary>
28            <pre>{this.state.error?.message}</pre>
29          </details>
30          <button
31            onClick={() => this.setState({ hasError: false })}
32          >
33            Try again
34          </button>
35        </div>
36      );
37    }
38
39    return this.props.children;
40  }
41}
42
43// Usage in layout
44export default function Layout({ children }) {
45  return (
46    <html>
47      <body>
48        <ServerErrorBoundary>
49          {children}
50        </ServerErrorBoundary>
51      </body>
52    </html>
53  );
54}

React Server Components fundamentally change how we think about React applications, offering better performance, automatic code splitting, and safer server-side operations.

Go to CodeWorlds