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.
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.
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.
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:
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.
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:
"use client" directive at the beginning of the fileThe 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.
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}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:
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.
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.
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}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}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}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.
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}When a Client Component needs to perform an action that involves the server, we use React Server Actions or API Routes.
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}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:
It's like the advanced quantum switches in Quantum Metropolis - they allow instant transitions between different layouts without interrupting system operations.
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}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}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}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}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}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}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:
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.