Jak zrozumieć React Server Components?
React Server Components (RSC) to nowa funkcjonalność w ekosystemie React, która pozwala na renderowanie komponentów Reacta wyłącznie po stronie serwera. W przeciwieństwie do tradycyjnych komponentów klienckich, które są renderowane w przeglądarce użytkownika, komponenty serwerowe są przetwarzane na serwerze, a wynikowy HTML jest przesyłany do klienta. To podejście przynosi wiele korzyści, takich jak zmniejszenie rozmiaru przesyłanych pakietów JavaScript, poprawa wydajności aplikacji oraz zwiększenie bezpieczeństwa poprzez utrzymanie wrażliwych operacji po stronie serwera.

- 1. Co tak naprawdę oznacza „renderowanie” w React?
- 2. Kontekst i ewolucja
- 3. Podstawy React Server Components
- 4. Komponentyzacja backendu
- 5. Jakie są różnice między komponentami serwerowymi a klienckimi?
- 6. Architektura hybrydowa i zagnieżdżanie komponentów
- 7. Automatyczne dzielenie kodu
- 8. Integracja z Next.js
- 9. SSR vs RSC vs SSG
- 10. Flight Payload i serializacja komponentów
- 11. Przekazywanie danych między komponentami
- 12. Kiedy nie używać React Server Components?
- 13. Korzyści z użycia React Server Components
- 14. Ograniczenia React Server Components
- 15. Przykładowe zastosowania React Server Components
- 16. Podsumowanie
Co tak naprawdę oznacza „renderowanie” w React?
W klasycznym rozumieniu „renderowanie” oznacza przekształcenie HTML i CSS w widoczne piksele na ekranie przez przeglądarkę. React jednak używa tego pojęcia inaczej — jako procesu wywoływania funkcji komponentów w celu zbudowania struktury wirtualnego DOM-u (Virtual DOM), czyli wewnętrznej reprezentacji tego, jak interfejs powinien wyglądać.
Oznacza to, że kiedy mówimy o „renderowaniu” w React, mamy na myśli obliczenie, jaki powinien być wynik JSX w postaci drzewa elementów Reacta – jeszcze zanim cokolwiek zostanie narysowane na ekranie. Samo „malowanie” (paint) interfejsu odbywa się dopiero później, po porównaniu drzewa wirtualnego z istniejącym DOM i zastosowaniu zmian.
Zrozumienie tego rozróżnienia między „renderem” w React a klasycznym renderem przeglądarkowym jest kluczowe, by prawidłowo interpretować sposób działania Server Components.
Kontekst i ewolucja
React od początku był biblioteką renderującą po stronie klienta. Wraz ze wzrostem złożoności aplikacji, zaczęły pojawiać się potrzeby wydajniejszego przesyłania danych, lepszej optymalizacji ładowania oraz bezpieczeństwa. Początkowo odpowiedzią na te potrzeby było Server-Side Rendering (SSR), które jednak nadal wymagało hydratacji po stronie klienta. React Server Components zostały zaprojektowane jako uzupełnienie tych rozwiązań, umożliwiając jeszcze większą kontrolę nad tym, co trafia do przeglądarki użytkownika.
Podstawy React Server Components
Komponenty serwerowe umożliwiają bezpośredni dostęp do zasobów serwera, takich jak bazy danych czy system plików, bez konieczności tworzenia dodatkowych API. Poniżej przedstawiono przykład prostego komponentu serwerowego, który pobiera dane z bazy danych:
import db from 'imaginary-db';
async function ServerComponent() {
const data = await db.query('SELECT * FROM table');
return (
<div>
{data.map((item) => (
<p key={item.id}>{item.name}</p>
))}
</div>
);
}
export default ServerComponent;
W tym przykładzie komponent ServerComponent wykonuje zapytanie do bazy danych i renderuje wyniki. Cały proces odbywa się na serwerze, a klient otrzymuje już wygenerowany HTML. Warto dodać, że bezpośrednie zapytania SQL w komponencie są niezalecane. Dobrą praktyką byłoby zastosowanie warstwy abstrakcji (np. ORM – Prisma, Drizzle).
Komponentyzacja backendu
React Server Components można traktować jako nową formę budowania logiki backendowej – komponentową, deklaratywną i zintegrowaną z interfejsem użytkownika. Zamiast tworzyć osobne endpointy API i zarządzać ich stanem oraz odpowiedziami po stronie klienta, komponent serwerowy może bezpośrednio komunikować się z bazą danych lub usługami backendowymi, a wynik przekazywać do UI w postaci gotowego HTML.
To podejście prowadzi do znacznie ciaśniejszej integracji między logiką backendową a komponentami UI i redukuje potrzebę korzystania z pośrednich warstw, takich jak REST czy GraphQL – szczególnie w aplikacjach tworzonych przez jeden zespół front–back. Dzięki temu komponent serwerowy może pełnić jednocześnie rolę kontrolera, źródła danych i szablonu widoku, co przybliża architekturę do modelu „server-first UI”. To oznacza mniejsze rozdrobnienie odpowiedzialności i łatwiejsze utrzymanie kodu w projektach typu fullstack.
Jakie są różnice między komponentami serwerowymi a klienckimi?
W nowym paradygmacie Reacta wszystkie komponenty są domyślnie traktowane jako serwerowe. Aby oznaczyć komponent jako kliencki, należy na początku pliku dodać dyrektywę 'use client’. Przykład:
'use client';
import React, { useState } from 'react';
function ClientComponent() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Kliknięto {count} razy
</button>
);
}
export default ClientComponent;
Komponenty klienckie są niezbędne do obsługi interakcji użytkownika, takich jak zarządzanie stanem (useState) czy efekty uboczne (useEffect). Natomiast komponenty serwerowe nie mają dostępu do hooków i są renderowane tylko raz — na serwerze. Nie są ponownie renderowane po stronie klienta i nie reagują na zdarzenia, co oznacza, że nie mogą być używane do obsługi interaktywnych funkcji.
Architektura hybrydowa i zagnieżdżanie komponentów
Komponenty serwerowe mogą zawierać komponenty klienckie – to pozwala tworzyć strukturę aplikacji, w której dane są pobierane i przetwarzane na serwerze, a interakcje obsługiwane są po stronie klienta.
Przykład:
Komponent serwerowy (ServerComponent.jsx):
import ClientButton from './ClientButton';
async function ServerComponent() {
const data = await fetch('https://api.example.com/data').then(res => res.json());
return (
<div>
<h2>Dane z API:</h2>
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<ClientButton />
</div>
);
}
export default ServerComponent;
Komponent kliencki (ClientButton.jsx):
'use client';
import { useState } from 'react';
export default function ClientButton() {
const [clicked, setClicked] = useState(false);
return (
<button onClick={() => setClicked(true)}>
{clicked ? 'Kliknięto!' : 'Kliknij mnie'}
</button>
);
}
W niektórych sytuacjach komponent serwerowy może być „wstrzyknięty” do komponentu klienckiego. Przykład:
// ServerComponent.tsx (Server Component)
export default function ServerComponent() {
// Server-side data fetching or logic
const data = fetchDataOnServer();
return (
<div>
<h2>Server Component</h2>
<p>{data}</p>
</div>
);
}
// ClientComponent.tsx (Client Component)
'use client';
import { useState } from 'react';
export default function ClientComponent({ children }) {
const [count, setCount] = useState(0);
return (
<div>
<h1>Client Component</h1>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
{/* Server Component is rendered here */}
<div className="server-content">
{children}
</div>
</div>
);
}
// Page.tsx (Parent Server Component)
import ClientComponent from './ClientComponent';
import ServerComponent from './ServerComponent';
export default function Page() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}
Powyższy przykład pokazuje zalecany wzorzec przekazywania komponentu serwerowego do komponentu klienckiego z wykorzystaniem propsa children. W tym podejściu komponent serwerowy nie jest bezpośrednio importowany w pliku komponentu klienckiego, co byłoby niedozwolone. Zamiast tego, to komponent nadrzędny (serwerowy) renderuje strukturę ClientComponent zagnieżdżając wewnątrz ServerComponent. Dzięki temu komponent kliencki może wyświetlić fragment serwerowy w odpowiednim miejscu, zachowując zgodność z zasadami RSC.
Automatyczne dzielenie kodu
React automatycznie rozdziela kod serwerowy i kliencki. Komponenty serwerowe nigdy nie są przesyłane do przeglądarki, pozostają wyłącznie na serwerze. Natomiast komponenty oznaczone jako 'use client’ są bundlowane i wysyłane do klienta z pełnym kodem JavaScript, gotowym do hydratacji.
Dzięki temu nie ma potrzeby ręcznego konfigurowania podziału kodu, jak w tradycyjnym SSR. Klient otrzymuje dokładnie to, co potrzebne — bez zbędnego narzutu, co znacząco poprawia wydajność i czas ładowania aplikacji.
Integracja z Next.js
Next.js od wersji 13.4+ wprowadził wsparcie dla React Server Components w ramach nowego systemu routingu „App Router”. Dzięki temu deweloperzy mogą korzystać z komponentów serwerowych w aplikacjach Next.js, co pozwala na efektywne zarządzanie renderingiem po stronie serwera oraz klienta.
SSR vs RSC vs SSG
W świecie Reacta istnieje kilka podejść do renderowania zawartości po stronie serwera, które często bywają ze sobą mylone.
- SSR (Server-Side Rendering) polega na tym, że serwer wykonuje kod komponentów i generuje z niego statyczny HTML, który jest wysyłany do klienta. Jednak, aby aplikacja była interaktywna, klient nadal musi pobrać i wykonać cały kod JavaScript oraz przeprowadzić hydratację – czyli odtworzyć wirtualny DOM w przeglądarce i przypisać zdarzenia.
- SSG (Static Site Generation) to odmiana SSR, w której proces renderowania odbywa się podczas budowania aplikacji – HTML jest generowany raz, a potem serwowany jako plik statyczny. To rozwiązanie jest idealne dla treści, które rzadko się zmieniają.
- RSC (React Server Components) idą o krok dalej – renderowanie odbywa się na serwerze, ale zamiast przesyłać cały kod komponentów i wykonywać go ponownie po stronie klienta, serwer przesyła tylko HTML oraz tzw. Flight Payload – zserializowaną strukturę React Elements. Dzięki temu komponenty serwerowe nie muszą być wysyłane jako kod JavaScript do klienta, co znacząco zmniejsza rozmiar bundla i poprawia wydajność.
To rozróżnienie pozwala lepiej zrozumieć, kiedy i dlaczego warto sięgnąć po konkretne podejście w zależności od potrzeb aplikacji.
Flight Payload i serializacja komponentów
Kluczowym mechanizmem, który pozwala React Server Components działać bez konieczności przesyłania kodu JavaScript do klienta, jest Flight Payload – specjalny format danych, który zawiera zserializowaną strukturę komponentów.
Podczas renderowania komponentu serwerowego na serwerze, wynik funkcji (czyli struktura elementów Reacta) jest konwertowany do postaci tekstowej – JSON-owego drzewa opisującego typy elementów, propsy i ich hierarchię. To zserializowane drzewo jest następnie wysyłane do przeglądarki jako ładunek danych (payload).
Po stronie klienta React odczytuje te dane i buduje z nich Virtual DOM, bez konieczności wykonywania kodu komponentu serwerowego. Dzięki temu można utrzymać pełną logikę serwerową poza przeglądarką, jednocześnie zapewniając Reactowi możliwość aktualizacji i zarządzania strukturą UI.
Flight Payload jest podstawą działania React Server Components i tym, co umożliwia pełną separację kodu serwerowego od kodu klienckiego przy zachowaniu spójnego modelu komponentów.
Przekazywanie danych między komponentami
Komponent serwerowy może przekazywać dane do komponentu klienckiego za pomocą propsów. Przykład:
// ServerComponent.jsx
import ClientCard from './ClientCard';
async function ServerComponent() {
const user = await getUser();
return <ClientCard name={user.name} />;
}
export default ServerComponent;
// ClientCard.jsx
'use client';
function ClientCard({ name }) {
return <div>Witaj, {name}!</div>;
}
export default ClientCard;
Kiedy nie używać React Server Components?
- w prostych aplikacjach typu SPA bez SSR.
- w środowiskach statycznego hostingu (np. GitHub Pages).
- w aplikacjach, gdzie każdy komponent wymaga dynamicznej interakcji.
Przykład praktyczny: Załóżmy, że chcemy stworzyć aplikację wyświetlającą listę postów z możliwością dodawania nowych. Możemy wykorzystać komponent serwerowy do pobierania i renderowania listy postów oraz komponent kliencki do obsługi interakcji użytkownika:
Komponent serwerowy
import db from 'imaginary-db';
import AddPostButton from './AddPostButton';
async function PostsList() {
const posts = await db.query('SELECT * FROM posts');
return (
<div>
{posts.map((post) => (
<p key={post.id}>{post.title}</p>
))}
<AddPostButton />
</div>
);
}
export default PostsList;
Komponent kliencki
'use client';
import { useState } from 'react';
function AddPostButton() {
const [title, setTitle] = useState('');
const handleAddPost = async () => {
await fetch('/api/addPost', {
method: 'POST',
body: JSON.stringify({ title }),
});
setTitle('');
};
return (
<div>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Nowy post"
/>
<button onClick={handleAddPost}>Dodaj post</button>
</div>
);
}
export default AddPostButton;
Korzyści z użycia React Server Components
- wydajność: Zmniejszenie ilości kodu JavaScript przesyłanego do klienta prowadzi do szybszego ładowania stron i lepszej wydajności aplikacji.
- bezpieczeństwo: Przechowywanie wrażliwych operacji, takich jak zapytania do bazy danych, po stronie serwera zwiększa bezpieczeństwo aplikacji.
- uproszczenie kodu: Bezpośredni dostęp do zasobów serwera z poziomu komponentów React upraszcza architekturę aplikacji, eliminując potrzebę tworzenia dodatkowych API.
Ograniczenia React Server Components
- brak możliwości użycia hooków takich jak useState, useEffect.
- nie mogą zawierać interakcji – wymagają komponentów klienckich.
- nie działają poza środowiskiem wspierającym RSC (np. starsze wersje CRA).
- brak możliwości dynamicznego importowania komponentów serwerowych w kodzie klienckim.
Przykładowe zastosowania React Server Components
React Server Components szczególnie dobrze sprawdzają się w aplikacjach, które intensywnie korzystają z danych, ale nie wymagają dużej liczby interakcji po stronie użytkownika. Do typowych zastosowań należą:
- dashboardy administracyjne z danymi pobieranymi z wielu źródeł,
- panele CMS z dynamicznym generowaniem treści,
- sklepy e-commerce, gdzie dane o produktach mogą być pobierane na serwerze,
- blogi i portale treściowe, gdzie szybkość ładowania i SEO mają kluczowe znaczenie.
Podsumowanie
React Server Components wprowadzają nowy sposób myślenia o renderowaniu w aplikacjach React, łącząc zalety renderowania po stronie serwera z elastycznością komponentów klienckich. Dzięki nim możliwe jest tworzenie bardziej wydajnych, bezpiecznych i prostszych w utrzymaniu aplikacji. Przy odpowiednim zastosowaniu RSC mogą znacząco poprawić doświadczenie użytkownika i komfort pracy deweloperskiej.


