We use cookies to enhance your experience on the site
CodeWorlds

Component System - Client and Server Components

In Quantum Metropolis, quantum system architects always have to make fundamental decisions: which processes to run on central quantum processors (for tasks requiring the highest computational power), and which on citizens' end devices (for instant interactivity). This dichotomy perfectly reflects the revolutionary change introduced by Next.js 15 - the division into Server Components and Client Components.

The React Server Components Revolution

React Server Components (RSC) represent the biggest paradigm shift in React since the introduction of Hooks. Before their appearance, all React components were rendered on the server or client as a cohesive whole. Now we can create applications where some components are rendered exclusively on the server, others on the client, and data and UI structures flow seamlessly between them.

It's like the difference between traditional transport systems in Quantum Metropolis, where vehicles move only along specific routes, and the new quantum transport system, where matter can be teleported directly to its destination.

Server Components - Basics

In the App Router of Next.js 15, all components are Server Components by default, unless we explicitly mark them as Client Components.

Server Components are rendered on the server and don't send any JavaScript to the browser. It's like the quantum computing processors of Quantum Metropolis, which perform complex calculations and transmit only the final results to citizens' devices.

Advantages of Server Components:

  1. Access to server resources - direct access to databases, file system, and other server resources
  2. Security - sensitive data and logic remain on the server
  3. Automatic code splitting - only necessary code is sent to the client
  4. Smaller JavaScript bundle size - no need to send rendering code
  5. Better SEO - faster static rendering
  6. Better first load performance - less JavaScript to download and execute

Server Component Example:

1// app/dashboard/stats/page.tsx - Server Component (by default)
2export default async function StatsPage() {
3  // This code runs ONLY on the server
4  const stats = await fetchQuantumSystemStats();
5
6  return (
7    <section className="quantum-stats">
8      <h1>Quantum Systems Statistics</h1>
9      <div className="stats-grid">
10        {stats.map(stat => (
11          <StatCard key={stat.id} name={stat.name} value={stat.value} />
12        ))}
13      </div>
14    </section>
15  );
16}

Note several important aspects:

  1. Using async/await directly in the component - this is only possible in Server Components
  2. No use of React hooks (useState, useEffect, etc.) - they are not available in Server Components
  3. No event handling - Server Components cannot handle user events

Client Components - Basics

Client Components are rendered in the user's browser and provide interactivity. To mark a component as a Client Component, we add the

"use client"
directive at the top of the file.

It's like the personal quantum devices of Quantum Metropolis citizens - lighter, but instantly responsive to user actions.

Advantages of Client Components:

  1. Interactivity - handling user events (clicks, text input, etc.)
  2. React hooks - ability to use useState, useEffect, useContext, and other hooks
  3. Access to browser APIs - window, document, localStorage, etc.
  4. Client-side effects - animations, real-time interactions

Client Component Example:

1// app/dashboard/interactive-controls.tsx
2"use client"; // This directive marks the client component
3
4import { useState, useEffect } from 'react';
5
6export default function InteractiveControls() {
7  // This code runs in the user's browser
8  const [activeSystem, setActiveSystem] = useState('energy');
9  const [status, setStatus] = useState('idle');
10
11  useEffect(() => {
12    // Effects run only on the client
13    const timer = setTimeout(() => {
14      setStatus('ready');
15    }, 1000);
16
17    return () => clearTimeout(timer);
18  }, []);
19
20  return (
21    <div className="control-panel">
22      <h2>System: {activeSystem}</h2>
23      <p>Status: {status}</p>
24      <div className="button-group">
25        <button onClick={() => setActiveSystem('transport')}>
26          Transport
27        </button>
28        <button onClick={() => setActiveSystem('energy')}>
29          Energy
30        </button>
31        <button onClick={() => setActiveSystem('security')}>
32          Security
33        </button>
34      </div>
35    </div>
36  );
37}

Key aspects:

  1. The
    "use client"
    directive
    at the beginning of the file
  2. Using React hooks (useState, useEffect)
  3. Event handling (onClick)

Combining Server and Client Components

The most powerful feature of the new model is the ability to combine Server and Client Components in a single application. Server Components can render Client Components, but not the other way around (with some exceptions using patterns). It's like the hybrid transport system of Quantum Metropolis, where main routes are served by quantum teleportation, and the last mile by personal vehicles.

Example of combining components:

1// app/dashboard/page.tsx - Server Component
2export default async function DashboardPage() {
3  // This code runs ONLY on the server
4  const dashboardData = await fetchDashboardData();
5
6  return (
7    <main className="quantum-dashboard">
8      <h1>Quantum Metropolis Control Panel</h1>
9
10      {/* Server Component */}
11      <SystemStatusPanel systemsData={dashboardData.systems} />
12
13      {/* Client Component (imported and used in Server Component) */}
14      <InteractiveControls />
15    </main>
16  );
17}
1// app/dashboard/system-status-panel.tsx - Server Component (by default)
2export default function SystemStatusPanel({ systemsData }) {
3  return (
4    <div className="system-status-panel">
5      <h2>System Status</h2>
6      <div className="status-grid">
7        {systemsData.map(system => (
8          <SystemStatusCard key={system.id} system={system} />
9        ))}
10      </div>
11    </div>
12  );
13}
1// app/dashboard/system-status-card.tsx - Server Component (by default)
2import { StatusIndicator } from './status-indicator';
3
4export default function SystemStatusCard({ system }) {
5  return (
6    <div className="status-card">
7      <h3>{system.name}</h3>
8      <p>Performance: {system.performance}%</p>
9
10      {/* Client Component used in Server Component */}
11      <StatusIndicator status={system.status} />
12    </div>
13  );
14}
1// app/dashboard/status-indicator.tsx - Client Component
2"use client";
3
4import { useState, useEffect } from 'react';
5
6export function StatusIndicator({ status }) {
7  const [blinking, setBlinking] = useState(false);
8
9  useEffect(() => {
10    const interval = setInterval(() => {
11      setBlinking(prev => !prev);
12    }, 500);
13
14    return () => clearInterval(interval);
15  }, []);
16
17  return (
18    <div className={`status-indicator ${status} ${blinking ? 'blink' : ''}`}>
19      {status === 'active' ? 'Active' : 'Inactive'}
20    </div>
21  );
22}

Optimization Strategies When Designing Components

Properly deciding which components should be Server Components and which should be Client Components is crucial for application performance. Here are some strategies to help you make these decisions:

1. Server Component as the Default Choice

Always start with Server Components unless you need features available only in Client Components. This is similar to the approach in Quantum Metropolis, where central computing units are used by default unless instant interactivity is needed.

2. Granular Interactivity

Instead of marking an entire large component as "use client", extract only the interactive parts into separate Client Components, leaving the rest as Server Components.

Before optimization:

1// LESS OPTIMAL - entire component is a Client Component
2"use client";
3
4export default function ControlPanel() {
5  const [activeTab, setActiveTab] = useState('overview');
6
7  return (
8    <div className="control-panel">
9      <h2>Control Panel</h2>
10      <p>This panel allows you to manage quantum systems.</p>
11
12      <div className="tabs">
13        <button onClick={() => setActiveTab('overview')}>Overview</button>
14        <button onClick={() => setActiveTab('settings')}>Settings</button>
15      </div>
16
17      <div className="tab-content">
18        {activeTab === 'overview' ? (
19          <OverviewContent /> // Lots of static content
20        ) : (
21          <SettingsContent /> // Lots of static content
22        )}
23      </div>
24    </div>
25  );
26}

After optimization:

1// BETTER - split into Client and Server Components
2
3// control-panel.tsx - Server Component
4export default function ControlPanel() {
5  return (
6    <div className="control-panel">
7      <h2>Control Panel</h2>
8      <p>This panel allows you to manage quantum systems.</p>
9
10      <TabController /> {/* Only this part is a Client Component */}
11    </div>
12  );
13}
14
15// tab-controller.tsx - Client Component
16"use client";
17import { useState } from 'react';
18import { OverviewContent } from './overview-content';
19import { SettingsContent } from './settings-content';
20
21export function TabController() {
22  const [activeTab, setActiveTab] = useState('overview');
23
24  return (
25    <>
26      <div className="tabs">
27        <button onClick={() => setActiveTab('overview')}>Overview</button>
28        <button onClick={() => setActiveTab('settings')}>Settings</button>
29      </div>
30
31      <div className="tab-content">
32        {activeTab === 'overview' ? (
33          <OverviewContent /> // Imports Server Component
34        ) : (
35          <SettingsContent /> // Imports Server Component
36        )}
37      </div>
38    </>
39  );
40}

3. "Container and Presentation" Pattern

Use Server Components as "containers" for fetching data and organization, and Client Components as "presentation components" responsible for interactivity.

1// products-container.tsx - Server Component (container)
2export default async function ProductsContainer() {
3  const products = await fetchProducts();
4
5  return <ProductsList products={products} />;
6}
7
8// products-list.tsx - Client Component (presentation)
9"use client";
10import { useState } from 'react';
11
12export function ProductsList({ products }) {
13  const [sortBy, setSortBy] = useState('name');
14  const sortedProducts = [...products].sort((a, b) => a[sortBy].localeCompare(b[sortBy]));
15
16  return (
17    <div>
18      <div className="sorting">
19        <button onClick={() => setSortBy('name')}>Sort by name</button>
20        <button onClick={() => setSortBy('price')}>Sort by price</button>
21      </div>
22
23      <ul className="products">
24        {sortedProducts.map(product => (
25          <li key={product.id}>{product.name} - {product.price}</li>
26        ))}
27      </ul>
28    </div>
29  );
30}

Data Flow Between Server and Client Components

Passing data between server and client requires a special approach. In the RSC model, data flows in one direction: from server to client. It's like one-way quantum channels in Quantum Metropolis - information flows only from the central hub to personal devices.

Passing Data to Client Components:

1// server-component.tsx - Server Component
2async function ServerComponent() {
3  const data = await fetchSensitiveData();
4
5  // Pass processed data to client
6  return <ClientComponent processedData={data.publicPart} />;
7}
8
9// client-component.tsx - Client Component
10"use client";
11
12export function ClientComponent({ processedData }) {
13  // Using data passed from the server
14  return <div>{processedData.map(item => <p key={item.id}>{item.name}</p>)}</div>;
15}

Handling User Actions from Client Components:

When a Client Component needs to perform an action that involves the server, we use React Server Actions or API Routes.

Using Server Actions (new feature in Next.js 15):

1// actions.js - Server Actions
2"use server";
3
4export async function updateSystemStatus(systemId, newStatus) {
5  // This function runs on the server
6  await database.systems.update(systemId, { status: newStatus });
7  return { success: true };
8}
9
10// client-component.tsx - Client Component
11"use client";
12import { updateSystemStatus } from './actions';
13
14export function StatusToggle({ systemId, initialStatus }) {
15  const [status, setStatus] = useState(initialStatus);
16
17  async function handleToggle() {
18    const newStatus = status === 'active' ? 'inactive' : 'active';
19    const result = await updateSystemStatus(systemId, newStatus);
20
21    if (result.success) {
22      setStatus(newStatus);
23    }
24  }
25
26  return (
27    <button onClick={handleToggle}>
28      {status === 'active' ? 'Deactivate' : 'Activate'}
29    </button>
30  );
31}

Differences in Reloading and Navigation

In the traditional React model, page reloading or navigation often meant restarting the entire application. In the React Server Components model, navigation is more optimal:

  • Server Components are re-rendered on the server during navigation
  • Client Components preserve their state during navigation within the same application
  • Page transitions are smoother thanks to the "instant navigation" mechanism

It's like the advanced quantum switches in Quantum Metropolis - they allow instant transitions between different layouts without interrupting system operations.

Common Mistakes and Pitfalls

1. Using React hooks in Server Components

1// WRONG - Server Component trying to use React hooks
2export default function ServerComponent() {
3  // This will cause an error!
4  const [count, setCount] = useState(0);
5
6  return <div>Count: {count}</div>;
7}

2. Attempting to access browser APIs in Server Components

1// WRONG - Server Component trying to use browser APIs
2export default function ServerComponent() {
3  // This will cause an error!
4  const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 0;
5
6  return <div>Width: {windowWidth}px</div>;
7}

3. Passing functions to Server Components from Client Components

1// WRONG - Client Component trying to pass callback to Server Component
2"use client";
3
4export default function ClientComponent() {
5  const handleClick = () => console.log("Clicked!");
6
7  // This won't work! Server Components cannot accept functions as props
8  return <ServerComponent onClick={handleClick} />;
9}

Advanced Patterns for Using Server and Client Components

1. Provider Pattern

If you need to share state between different Client Components, use a Client Provider at a high level. This is similar to central synchronization units in Quantum Metropolis, which coordinate the actions of different personal devices.

1// providers.tsx - Client Component
2"use client";
3import { createContext, useState } from 'react';
4
5export const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
6
7export function ThemeProvider({ children }) {
8  const [theme, setTheme] = useState('light');
9  const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
10
11  return (
12    <ThemeContext.Provider value={{ theme, toggleTheme }}>
13      {children}
14    </ThemeContext.Provider>
15  );
16}
1// layout.tsx - Server Component
2import { ThemeProvider } from './providers';
3
4export default function RootLayout({ children }) {
5  return (
6    <html>
7      <body>
8        <ThemeProvider>
9          {children}
10        </ThemeProvider>
11      </body>
12    </html>
13  );
14}

2. "Lifting State Up" Pattern

When you have several Client Components that need access to the same state, lift the state to a common Client Component ancestor, not to a Server Component.

1// parent.tsx - Server Component
2export default function ParentServerComponent() {
3  return <ClientParent />;
4}
5
6// client-parent.tsx - Client Component (common ancestor)
7"use client";
8import { useState } from 'react';
9
10export function ClientParent() {
11  const [sharedState, setSharedState] = useState('initial');
12
13  return (
14    <div>
15      <ClientChildA sharedState={sharedState} setSharedState={setSharedState} />
16      <ClientChildB sharedState={sharedState} />
17    </div>
18  );
19}

3. Strategic Use of Suspense

Use the Suspense component to manage loading states in a hybrid application:

1// page.tsx - Server Component
2import { Suspense } from 'react';
3
4export default function Page() {
5  return (
6    <div>
7      <h1>Quantum Metropolis System Data</h1>
8
9      <Suspense fallback={<div>Loading data...</div>}>
10        <AsyncDataComponent />
11      </Suspense>
12    </div>
13  );
14}
15
16// async-data-component.tsx - Server Component with asynchronous code
17async function AsyncDataComponent() {
18  // This component can be suspended during loading
19  const data = await fetchData();
20
21  return <div>{/* Rendering data */}</div>;
22}

Summary

The component system in Next.js 15 based on React Server Components is a revolutionary change that combines the advantages of server rendering with client-side interactivity. Just as in Quantum Metropolis, where quantum teleportation and personal vehicles create a hybrid transport system, in Next.js server and client components work together, creating an efficient, scalable, and user-friendly interface.

When designing Next.js 15 applications:

  1. Use Server Components by default for better performance and security
  2. Switch to Client Components only when you need interactivity, hooks, or browser APIs
  3. Split components granularly to minimize the amount of JavaScript sent to the client
  4. Remember the one-way data flow - from server to client
  5. Use Server Actions for form processing and other user actions

Proper use of Server and Client Components will allow you to create next-generation Next.js applications that combine fast loading, SEO performance, and rich user interfaces - without the compromises that were necessary in traditional React applications.

In the next module, we will move to practice and create our first "Hello World" application using Next.js 15 and App Router, where we'll apply the knowledge gained about Server and Client Components.

Go to CodeWorlds