W Metropolii Quantum, architekci systemów kwantowych zawsze muszą podejmować fundamentalne decyzje: które procesy uruchomić na centralnych procesorach kwantowych (dla zadań wymagających najwyższej mocy obliczeniowej), a które na urządzeniach końcowych obywateli (dla natychmiastowej interaktywności). Ta dychotomia doskonale odzwierciedla rewolucyjną zmianę wprowadzoną przez Next.js 15 - podział na Server Components i Client Components.
React Server Components (RSC) stanowią największą zmianę paradygmatu w React od czasu wprowadzenia Hooks. Przed ich pojawieniem się, wszystkie komponenty React były renderowane na serwerze lub kliencie jako spójna całość. Teraz możemy tworzyć aplikacje, w których niektóre komponenty są renderowane wyłącznie na serwerze, inne na kliencie, a dane i struktury UI płynnie między nimi przepływają.
To jak różnica między tradycyjnymi systemami transportowymi Metropolii Quantum, gdzie pojazdy poruszają się tylko po określonych trasach, a nowym systemem kwantowego transportu, gdzie materia może być teleportowana bezpośrednio do miejsca docelowego.
W App Router Next.js 15, wszystkie komponenty są domyślnie Server Components, chyba że jawnie oznaczymy je jako Client Components.
Server Components są renderowane na serwerze i nie wysyłają żadnego JavaScript do przeglądarki. To jak kwantowe procesory obliczeniowe Metropolii Quantum, które wykonują skomplikowane obliczenia i przesyłają tylko końcowe wyniki do urządzeń obywateli.
1// app/dashboard/stats/page.tsx - Server Component (domyślnie)
2export default async function StatsPage() {
3 // Ten kod wykonuje się TYLKO na serwerze
4 const stats = await fetchQuantumSystemStats();
5
6 return (
7 <section className="quantum-stats">
8 <h1>Statystyki Systemów Kwantowych</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}Zwróć uwagę na kilka ważnych aspektów:
Client Components są renderowane w przeglądarce użytkownika i zapewniają interaktywność. Aby oznaczyć komponent jako Client Component, dodajemy na górze pliku dyrektywę
"use client".To jak personalne urządzenia kwantowe obywateli Metropolii Quantum - lżejsze, ale natychmiastowo reagujące na akcje użytkownika.
1// app/dashboard/interactive-controls.tsx
2"use client"; // Ta dyrektywa oznacza komponent kliencki
3
4import { useState, useEffect } from 'react';
5
6export default function InteractiveControls() {
7 // Ten kod wykonuje się w przeglądarce użytkownika
8 const [activeSystem, setActiveSystem] = useState('energy');
9 const [status, setStatus] = useState('idle');
10
11 useEffect(() => {
12 // Efekty działają tylko na kliencie
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 Energia
30 </button>
31 <button onClick={() => setActiveSystem('security')}>
32 Bezpieczeństwo
33 </button>
34 </div>
35 </div>
36 );
37}Kluczowe aspekty:
"use client" na początku plikuNajpotężniejszą cechą nowego modelu jest możliwość łączenia Server i Client Components w jednej aplikacji. Server Components mogą renderować Client Components, ale nie odwrotnie (z pewnymi wyjątkami przy użyciu wzorców). Jest to jak hybrydowy system transportu Metropolii Quantum, gdzie główne trasy są obsługiwane przez teleportację kwantową, a ostatni odcinek przez personalne pojazdy.
1// app/dashboard/page.tsx - Server Component
2export default async function DashboardPage() {
3 // Ten kod wykonuje się TYLKO na serwerze
4 const dashboardData = await fetchDashboardData();
5
6 return (
7 <main className="quantum-dashboard">
8 <h1>Panel Kontrolny Metropolii Quantum</h1>
9
10 {/* Server Component */}
11 <SystemStatusPanel systemsData={dashboardData.systems} />
12
13 {/* Client Component (importowany i używany w Server Component) */}
14 <InteractiveControls />
15 </main>
16 );
17}1// app/dashboard/system-status-panel.tsx - Server Component (domyślnie)
2export default function SystemStatusPanel({ systemsData }) {
3 return (
4 <div className="system-status-panel">
5 <h2>Status Systemów</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 (domyślnie)
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>Wydajność: {system.performance}%</p>
9
10 {/* Client Component używany w 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' ? 'Aktywny' : 'Nieaktywny'}
20 </div>
21 );
22}Właściwe decydowanie, które komponenty powinny być Server Components, a które Client Components, jest kluczowe dla wydajności aplikacji. Oto kilka strategii, które pomogą Ci podejmować te decyzje:
Zawsze zaczynaj od Server Components, chyba że potrzebujesz funkcji dostępnych tylko w Client Components. To podobne do podejścia w Metropolii Quantum, gdzie domyślnie używa się centralnych jednostek obliczeniowych, chyba że potrzebna jest natychmiastowa interaktywność.
Zamiast oznaczać cały duży komponent jako "use client", wydziel tylko interaktywne części do osobnych Client Components, pozostawiając resztę jako Server Components.
1// MNIEJ OPTYMALNE - cały komponent jest 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>Panel Sterowania</h2>
10 <p>Ten panel pozwala na zarządzanie systemami kwantowymi.</p>
11
12 <div className="tabs">
13 <button onClick={() => setActiveTab('overview')}>Przegląd</button>
14 <button onClick={() => setActiveTab('settings')}>Ustawienia</button>
15 </div>
16
17 <div className="tab-content">
18 {activeTab === 'overview' ? (
19 <OverviewContent /> // Dużo statycznej zawartości
20 ) : (
21 <SettingsContent /> // Dużo statycznej zawartości
22 )}
23 </div>
24 </div>
25 );
26}1// LEPSZE - rozdzielenie na Client i Server Components
2
3// control-panel.tsx - Server Component
4export default function ControlPanel() {
5 return (
6 <div className="control-panel">
7 <h2>Panel Sterowania</h2>
8 <p>Ten panel pozwala na zarządzanie systemami kwantowymi.</p>
9
10 <TabController /> {/* Tylko ta część to 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')}>Przegląd</button>
28 <button onClick={() => setActiveTab('settings')}>Ustawienia</button>
29 </div>
30
31 <div className="tab-content">
32 {activeTab === 'overview' ? (
33 <OverviewContent /> // Importuje Server Component
34 ) : (
35 <SettingsContent /> // Importuje Server Component
36 )}
37 </div>
38 </>
39 );
40}Użyj Server Components jako "kontenerów" do pobierania danych i organizacji, a Client Components jako "komponentów prezentacyjnych" odpowiedzialnych za interaktywność.
1// products-container.tsx - Server Component (kontener)
2export default async function ProductsContainer() {
3 const products = await fetchProducts();
4
5 return <ProductsList products={products} />;
6}
7
8// products-list.tsx - Client Component (prezentacja)
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')}>Sortuj po nazwie</button>
20 <button onClick={() => setSortBy('price')}>Sortuj po cenie</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}Przesyłanie danych między serwerem a klientem wymaga specjalnego podejścia. W modelu RSC, dane płyną w jednym kierunku: z serwera do klienta. To jak jednokierunkowe kanały kwantowe w Metropolii Quantum - informacje płyną tylko z centrali do urządzeń osobistych.
1// server-component.tsx - Server Component
2async function ServerComponent() {
3 const data = await fetchSensitiveData();
4
5 // Przekazanie przetworzonych danych do klienta
6 return <ClientComponent processedData={data.publicPart} />;
7}
8
9// client-component.tsx - Client Component
10"use client";
11
12export function ClientComponent({ processedData }) {
13 // Używanie danych przekazanych z serwera
14 return <div>{processedData.map(item => <p key={item.id}>{item.name}</p>)}</div>;
15}Gdy Client Component musi wykonać akcję, która angażuje serwer, używamy React Server Actions lub API Routes.
1// actions.js - Server Actions
2"use server";
3
4export async function updateSystemStatus(systemId, newStatus) {
5 // Ta funkcja działa na serwerze
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' ? 'Dezaktywuj' : 'Aktywuj'}
29 </button>
30 );
31}W tradycyjnym modelu React, przeładowanie strony lub nawigacja często oznaczały ponowne uruchomienie całej aplikacji. W modelu React Server Components, nawigacja jest bardziej optymalna:
Jest to jak zaawansowane przełączniki kwantowe w Metropolii Quantum - pozwalają na natychmiastowe przejścia między różnymi układami bez przerywania działania systemów.
1// ŹLE - Server Component próbuje używać hooków React
2export default function ServerComponent() {
3 // To spowoduje błąd!
4 const [count, setCount] = useState(0);
5
6 return <div>Count: {count}</div>;
7}1// ŹLE - Server Component próbuje używać API przeglądarki
2export default function ServerComponent() {
3 // To spowoduje błąd!
4 const windowWidth = typeof window !== 'undefined' ? window.innerWidth : 0;
5
6 return <div>Width: {windowWidth}px</div>;
7}1// ŹLE - Client Component próbuje przekazać callback do Server Component
2"use client";
3
4export default function ClientComponent() {
5 const handleClick = () => console.log("Clicked!");
6
7 // To nie zadziała! Server Components nie mogą przyjmować funkcji jako props
8 return <ServerComponent onClick={handleClick} />;
9}Jeśli potrzebujesz współdzielić stan między różnymi Client Components, używaj Client Providera na wysokim poziomie. Jest to podobne do centralnych jednostek synchronizacyjnych w Metropolii Quantum, które koordynują działania różnych urządzeń osobistych.
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}Kiedy masz kilka Client Components, które potrzebują dostępu do tego samego stanu, podnieś stan do wspólnego przodka Client Component, a nie do Server Component.
1// parent.tsx - Server Component
2export default function ParentServerComponent() {
3 return <ClientParent />;
4}
5
6// client-parent.tsx - Client Component (wspólny przodek)
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}Użyj komponentu Suspense do zarządzania stanami ładowania w aplikacji hybrydowej:
1// page.tsx - Server Component
2import { Suspense } from 'react';
3
4export default function Page() {
5 return (
6 <div>
7 <h1>Dane systemowe Metropolii Quantum</h1>
8
9 <Suspense fallback={<div>Ładowanie danych...</div>}>
10 <AsyncDataComponent />
11 </Suspense>
12 </div>
13 );
14}
15
16// async-data-component.tsx - Server Component z asynchronicznym kodem
17async function AsyncDataComponent() {
18 // Ten komponent może być zawieszony (suspendowany) podczas ładowania
19 const data = await fetchData();
20
21 return <div>{/* Renderowanie danych */}</div>;
22}System komponentów w Next.js 15 oparty na React Server Components to rewolucyjna zmiana, która łączy zalety renderowania serwerowego z interaktywnością kliencką. Podobnie jak w Metropolii Quantum, gdzie kwantowa teleportacja i osobiste pojazdy tworzą hybrydowy system transportu, tak w Next.js komponenty serwerowe i klienckie współpracują, tworząc wydajny, skalowalny i przyjazny dla użytkownika interfejs.
Przy projektowaniu aplikacji Next.js 15:
Prawidłowe wykorzystanie Server i Client Components pozwoli Ci tworzyć aplikacje Next.js nowej generacji, które łączą szybkość ładowania, wydajność SEO i bogaty interfejs użytkownika - bez kompromisów, które były konieczne w tradycyjnych aplikacjach React.
W następnym module przejdziemy do praktyki i stworzymy naszą pierwszą aplikację "Hello World" z użyciem Next.js 15 i App Router, gdzie zastosujemy zdobytą wiedzę o Server i Client Components.