---
title: "React Server Components: co trafia do przeglądarki"
description: "React Server Components w Next.js 16 na żywym demo: co zostaje na serwerze, co trafia do przeglądarki jako JavaScript, a co niesie HTML i payload RSC."
canonical_url: "https://www.outofplace.space/pl/blog/react-server-components-co-trafia-do-przegladarki"
language: "pl"
last_updated: "2026-10-23"
alternates:
  en: "https://www.outofplace.space/blog/react-server-components-explained.md"
---

# React Server Components: co trafia do przeglądarki

> React Server Components w Next.js 16 na żywym demo: co zostaje na serwerze, co trafia do przeglądarki jako JavaScript, a co niesie HTML i payload RSC.

Opublikowano 2026-10-23 · Kategoria: Next.js i React · Tagi: React, Next.js

React Server Components (komponenty serwerowe) to komponenty, których kod działa tylko na serwerze. W App Routerze Next.js każda strona i każdy layout jest nim domyślnie, a przeglądarka dostaje to, co wyrenderowały, jako HTML i drugi raz w payloadzie RSC, nigdy ich kod. Jako JavaScript trafia do niej tylko plik zaczynający się od `"use client"` i wszystko, co on importuje. Takie Client Components i tak są najpierw renderowane do HTML na serwerze, a potem hydratowane w przeglądarce. Dane przechodzą z serwera do klienta tylko przez serializowalne propsy, a Server Component przekazany jako `children` zostaje wynikiem z serwera nawet wewnątrz Client Component. Wszystko to pokazuje jedno demo na Next.js 16.4.0 z React 19.3.0, a dziewięć testów w Playwright sprawdza zachowanie demo opisane w tym tekście. We własnej aplikacji używaj wersji z poprawkami: [poprawki RCE w React i Next.js z października 2026](https://www.outofplace.space/pl/blog/nowosci-web-dev-2026-10-18) dotyczą tej samej warstwy.

Demo na żywo: https://lab.outofplace.space/demos/react-server-components-explained (Next.js 16.4.0; kod z commita b5049c6: https://github.com/outof-place/outofplace-lab/tree/b5049c6980621147d3446a5748a1c6f1d74d4cbc/app/demos/react-server-components-explained)

## Czym są React Server Components?

Dokumentacja Reacta opisuje [Server Components](https://react.dev/reference/rsc/server-components) jako komponenty, które renderują się z wyprzedzeniem, „in an environment separate from your client app or SSR server”, czyli w środowisku oddzielonym od aplikacji w przeglądarce i od serwera SSR. W Next.js tym środowiskiem jest serwer, w czasie builda albo przy żądaniu. Komponentu nie oznacza się jako serwerowego: [Server Components nie mają dyrektywy](https://react.dev/reference/rsc/server-components#there-is-no-directive-for-server-components), a `"use server"` oznacza Server Functions. Oznaczasz drugą stronę, przez `"use client"`.

Przewodnik Next.js po [granicy między serwerem a klientem](https://nextjs.org/docs/app/guides/server-and-client-boundary#rendering-environments) ujmuje różnicę w jednej tabeli. Tu z tym, co z niej wynika przy pisaniu kodu:

| | Server Component | Client Component |
| --- | --- | --- |
| Oznaczenie | brak (domyślne w `app/`) | `"use client"` na początku pliku |
| Renderuje się na serwerze | tak | tak, do HTML |
| Działa w przeglądarce | nie | tak, tam się hydratuje |
| Jego kod i importy trafiają do JavaScriptu | nie | tak |
| `useState`, `useEffect`, `onClick` | nie | tak |
| Może czytać sekrety, pliki, bazę danych | tak | nie: traktuj go jak kod przeglądarki |

## Co trafia do przeglądarki: HTML, payload RSC i JavaScript klienta

Przy [pierwszym wczytaniu strony w Next.js](https://nextjs.org/docs/app/getting-started/server-and-client-components#on-the-client-first-load) dokumentacja wymienia trzy rzeczy, z których korzysta przeglądarka: HTML do szybkiego, nieinteraktywnego podglądu, payload RSC do uzgodnienia drzew serwera i klienta oraz JavaScript do hydratacji Client Components. Payload RSC to zwarta, zserializowana postać wyrenderowanego drzewa Server Components. Zawiera ich wynik, miejsca na Client Components z odwołaniami do ich plików JavaScript i wszystkie propsy, które Server Component przekazał do Client Component.

Server Component w demo podpisuje czas żądania kluczem HMAC. Klucz i moduł `crypto` z Node siedzą w osobnym pliku, który zaczyna się od `import "server-only"`:

app/demos/react-server-components-explained/signing.ts (https://github.com/outof-place/outofplace-lab/blob/b5049c6980621147d3446a5748a1c6f1d74d4cbc/app/demos/react-server-components-explained/signing.ts)

```ts
import "server-only";

import { createHmac } from "node:crypto";

// A made-up key, public in this repo. In an app it comes from process.env. Either way it lives
// only in the server module graph: import "server-only" turns an import from a "use client"
// file into a build error.
const SIGNING_KEY = "lab-demo-key-stays-on-the-server";

/** The first 12 hex characters of an HMAC-SHA256 of `value`, made with Node's crypto module. */
export function sign(value: string): string {
  return createHmac("sha256", SIGNING_KEY).update(value).digest("hex").slice(0, 12);
}
```

Karta, która z niego korzysta, to funkcja asynchroniczna bez dyrektywy:

app/demos/react-server-components-explained/server-card.tsx (https://github.com/outof-place/outofplace-lab/blob/b5049c6980621147d3446a5748a1c6f1d74d4cbc/app/demos/react-server-components-explained/server-card.tsx)

```tsx
import { io } from "next/cache";

import { sign } from "./signing";
import styles from "./rsc.module.css";

// Server Components: no directive, so they stay in the server module graph. The browser
// receives what they render (as HTML and in the RSC payload), never this code.

const renderedOn = () => (typeof window === "undefined" ? "server" : "browser");

export async function ServerCard() {
  // With Cache Components on, io() keeps what follows out of the build's static shell,
  // so the server signs a fresh time on every request.
  await io();
  const signedAt = new Date().toISOString();

  return (
    <section className={styles.card} aria-labelledby="server-component">
      <h2 id="server-component">Server Component</h2>
      <dl className={styles.rows}>
        <div>
          <dt>Rendered on</dt>
          <dd>{renderedOn()}</dd>
        </div>
        <div>
          <dt>Signed at</dt>
          <dd>
            <time dateTime={signedAt}>{signedAt.slice(11, 19)} UTC</time>
          </dd>
        </div>
        <div>
          <dt>Signature</dt>
          <dd>
            <code>{sign(signedAt)}</code>
          </dd>
        </div>
      </dl>
    </section>
  );
}

// Passed to the Client Component as children: it renders here, on the server, and the
// Client Component only places its output.
export function ServerNote() {
  return <span>{renderedOn()}, passed in as children</span>;
}
```

Testy sprawdzają na buildzie produkcyjnym, co naprawdę dociera:

- Odpowiedź HTML ma podpis w znacznikach i ten sam podpis drugi raz w skrypcie `<script>` wpisanym w tę samą odpowiedź: to payload RSC, bo przy pierwszym wczytaniu [payload RSC przychodzi razem z HTML](https://nextjs.org/docs/app/guides/server-and-client-boundary#state-and-interactivity).
- Podpis to HMAC-SHA256 pełnego znacznika czasu ISO, razem z datą i milisekundami, liczony z kluczem z `signing.ts`. Ten znacznik stoi w atrybucie `datetime` elementu `<time>`, a strona pokazuje z niego tylko godzinę, minuty i sekundy. Test czyta ten atrybut i sam liczy HMAC jeszcze raz.
- Gdy strona się wczytuje, hydratuje i przyjmuje trzy kliknięcia, klucza nie ma w żadnej odpowiedzi: ani w HTML, ani w payloadzie, ani w pliku JavaScript.
- Tekst z `counter.tsx` jest w pliku JavaScript. Tekstów, które istnieją tylko w `server-card.tsx` i `page.tsx`, nie ma w żadnym.

To samo sprawdzisz we własnej aplikacji. Otwórz źródło strony i wyszukaj wartość, którą wyrenderował Server Component: pojawi się w znacznikach i drugi raz w skrypcie. Potem w DevTools przeszukaj wczytane pliki JavaScript pod kątem tekstu, który jest tylko w źródle Server Component: nie powinno go tam być.

## Jak sprawdzić, który kod zostaje na serwerze

Decyduje jedna zasada: [kod przechodzi przez granicę przez importy](https://nextjs.org/docs/app/guides/server-and-client-boundary#crossing-the-boundary). Plik z `"use client"` to wejście do grafu modułów klienta, a każdy moduł, który importuje, trafia do bundla klienta, z własną dyrektywą albo bez niej. Plik, którego nie importuje żaden plik klienta, zostaje na serwerze.

Client Component w demo to licznik:

app/demos/react-server-components-explained/counter.tsx (https://github.com/outof-place/outofplace-lab/blob/b5049c6980621147d3446a5748a1c6f1d74d4cbc/app/demos/react-server-components-explained/counter.tsx)

```tsx
"use client";

import { useState, useSyncExternalStore, type ReactNode } from "react";

import styles from "./rsc.module.css";

const subscribe = () => () => {};

// A Client Component: "use client" puts this file and everything it imports in the browser's
// JavaScript. It still renders to HTML on the server first, then hydrates in the browser.
export function Counter({ children }: { children: ReactNode }) {
  const [count, setCount] = useState(0);
  // The server snapshot (false) is used on the server and during hydration; once hydrated,
  // React renders again with the browser's snapshot (true).
  const hydrated = useSyncExternalStore(subscribe, () => true, () => false);

  return (
    <section className={styles.card} aria-labelledby="client-component">
      <h2 id="client-component">Client Component</h2>
      <dl className={styles.rows}>
        <div>
          <dt>Rendered on</dt>
          <dd>{hydrated ? "browser, after hydration" : "server, not hydrated yet"}</dd>
        </div>
        <div>
          <dt>Count</dt>
          <dd>
            <output aria-label="Count">{count}</output>
            <button type="button" onClick={() => setCount((n) => n + 1)}>
              Add one
            </button>
          </dd>
        </div>
        <div>
          <dt>Children</dt>
          <dd>{children}</dd>
        </div>
      </dl>
    </section>
  );
}
```

Wiersz „Rendered on” pokazuje, że Client Component działa w obu miejscach. Z wyłączonym JavaScriptem przeglądarka pokazuje tylko HTML z serwera, a wiersz brzmi „server, not hydrated yet”, z licznikiem na 0. Z włączonym brzmi „browser, after hydration”. Przełącza go [`useSyncExternalStore`](https://react.dev/reference/react/useSyncExternalStore#adding-support-for-server-rendering), którego `getServerSnapshot` działa tylko na serwerze i podczas hydratacji. [Dokumentacja hydrateRoot w React](https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html) wymienia sprawdzanie `typeof window` w renderze jako częstą przyczynę błędów hydratacji, bo serwer i przeglądarka wyrenderowałyby co innego.

Stronę serwera zabezpiecza [`import "server-only"`](https://nextjs.org/docs/app/getting-started/server-and-client-components#preventing-environment-poisoning) w każdym module, który nigdy nie może trafić do przeglądarki. Zaimportowanie takiego modułu do Client Component kończy się wtedy błędem builda, a nie cichym wyciekiem. W Next.js nie trzeba instalować tej paczki: Next.js obsługuje ten import sam. Zmienne środowiskowe działają podobnie: do bundla klienta trafiają tylko te z prefiksem `NEXT_PUBLIC_`, a resztę Next.js zastępuje pustym napisem.

## Server Components w Client Components: wzorzec z children

Strona wkłada do licznika drugi Server Component, `ServerNote`, jako `children`:

app/demos/react-server-components-explained/page.tsx (https://github.com/outof-place/outofplace-lab/blob/b5049c6980621147d3446a5748a1c6f1d74d4cbc/app/demos/react-server-components-explained/page.tsx)

```tsx
import { Suspense } from "react";

import { Counter } from "./counter";
import { ServerCard, ServerNote } from "./server-card";
import styles from "./rsc.module.css";

// The page is a Server Component too: pages and layouts are, unless a file says "use client".
export default function Page() {
  return (
    <div className={styles.stack}>
      <Suspense fallback={<p className={styles.pending}>Signing on the server…</p>}>
        <ServerCard />
      </Suspense>

      <Counter>
        <ServerNote />
      </Counter>

      <p className={styles.hint}>
        Add one changes the count in the browser and leaves the signature as it is. <a href="">Reload</a> and the
        server signs a new time.
      </p>
    </div>
  );
}
```

Na zhydratowanej stronie notka nadal brzmi „server, passed in as children”, a trzy kliknięcia renderują licznik od nowa, nie zmieniając jej. Dokumentacja wyjaśnia dlaczego: wyrenderowany element Reacta da się zserializować, więc wynik Server Component przechodzi przez granicę jako dane, a Client Component tylko go umieszcza. [Server Components przekazane jako children](https://nextjs.org/docs/app/getting-started/server-and-client-components#interleaving-server-and-client-components) renderują się na serwerze z wyprzedzeniem, a ich wynik niesie payload RSC. Właścicielem `ServerNote` jest `Page` (w jego kodzie jest JSX), więc `ServerNote` renderuje się na serwerze; `Counter` jest tylko jego rodzicem w wyrenderowanym drzewie. Według zasady z dokumentacji zaimportowanie `ServerNote` w `counter.tsx` wciągnęłoby `server-card.tsx` do grafu modułów klienta, a za nim `signing.ts`, a `import "server-only"` zamieniłby to w błąd builda.

Tak samo używa się kontekstu Reacta: [Server Components nie obsługują kontekstu](https://nextjs.org/docs/app/getting-started/server-and-client-components#context-providers), więc provider to Client Component, który przyjmuje `children`, a drzewo wyrenderowane na serwerze trafia do środka.

## Czy RSC to to samo co SSR?

Nie, a demo pokazuje oba naraz. HTML licznika pochodzi z serwera: to renderowanie po stronie serwera, a [renderowanie po stronie serwera jest starsze niż RSC](https://nextjs.org/docs/app/guides/server-and-client-boundary), jak zauważa przewodnik po granicy. Next.js robi je w czasie builda albo przy żądaniu. Licznik pozostaje jednak Client Component, bo jego kod jest wysyłany i działa jeszcze raz w przeglądarce. „Renderowany na serwerze” mówi, jak powstał HTML. „Server Component” mówi, gdzie działa kod komponentu i czy jest wysyłany.

To, kiedy serwer renderuje, to osobna kwestia. Podpisany czas w demo powstaje przy każdym żądaniu: po przeładowaniu test widzi nowy. Lab działa z włączonymi [Cache Components](https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents), a [dokumentacja `await io()`](https://nextjs.org/docs/app/api-reference/functions/io) wyjaśnia, co to znaczy dla wartości takiej jak `new Date()`: zapisujesz ją raz [przez use cache w statycznej powłoce](https://www.outofplace.space/pl/blog/use-cache-w-nextjs-jak-dziala#jak-dlugo-zyje-wpis-use-cache) albo trzymasz poza statyczną powłoką przez `await io()`, jak robi to karta w `<Suspense>`.

## Dlaczego kliknięcie nie dociera do serwera, a przeładowanie tak

Trzy kliknięcia w Add one ustawiają licznik na 3 bez żadnego żądania sieciowego, a czas, podpis i notka zostają bez zmian. Według przewodnika po granicy Server Component może renderować się ponownie, gdy Next.js renderuje trasę od nowa, na przykład przy nawigacji, odświeżeniu albo po rewalidacji. W demo zmiana stanu w przeglądarce renderuje od nowa Client Component bez żadnego żądania do serwera i nie zmienia wyniku Server Components.

Po przeładowaniu serwer działa znowu: test widzi późniejszy czas, nowy podpis i licznik z powrotem na 0, bo pełne wczytanie zaczyna Client Component od stanu początkowego.

## Jak podzielić stronę Next.js na Server i Client Components

**1. Zacznij od Server Components.**
Strony i layouty w `app/` są domyślnie Server Components. Tam pobieraj dane i czytaj sekrety, bez żadnej dyrektywy.

**2. Daj use client najmniejszej interaktywnej części.**
Dodaj `"use client"` do pliku, który potrzebuje stanu, efektów, obsługi zdarzeń albo API przeglądarki, na przykład przycisku, filtra czy pola formularza, a nie do całej strony wokół. Wszystko, co ten plik importuje, też zostanie wysłane.

**3. Oznacz moduły serwerowe przez server-only.**
Dodaj `import "server-only"` do każdego modułu, który czyta sekret, bazę danych albo system plików. Błędny import zatrzyma wtedy build.

**4. Przekazuj małe, serializowalne propsy.**
Wysyłaj do Client Component tylko pola, które pokazuje: napisy, liczby, zwykłe obiekty, tablice, Date, Map, Set, Promise i JSX. Nigdy nie przekazuj całego rekordu z bazy ani zwykłej funkcji: przez granicę przechodzą tylko Server Functions.

**5. Przekazuj UI z serwera jako children.**
Gdy UI wyrenderowane na serwerze ma się znaleźć w Client Component, na przykład w modalu, panelu zakładki albo providerze kontekstu, przekaż je z Server Component jako `children` albo inny prop, zamiast je importować.

**6. Sprawdź build produkcyjny.**
Uruchom `next build` i `next start`, otwórz źródło strony i wyszukaj tekst, który ma zostać na serwerze, we wczytanym JavaScripcie, tak jak robi to test demo.

## Co psuje podział na serwer i klienta

### Propsy trafiają do przeglądarki

Każdy prop, który Server Component przekazuje do Client Component, jest serializowany do payloadu RSC. [Przewodnik Next.js po bezpieczeństwie danych](https://nextjs.org/docs/app/guides/data-security#passing-data-from-server-to-client) pokazuje typowy wyciek: strona przekazuje cały wiersz użytkownika do komponentu profilu z `"use client"`, a do przeglądarki trafia każda kolumna tego wiersza, także te, których komponent w ogóle nie pokazuje. Zwracaj z warstwy danych oznaczonej `server-only` mały obiekt tylko z publicznymi polami. API taint z Reacta potrafią zablokować przekazanie konkretnego obiektu albo wartości do klienta (w Next.js za opcją [`experimental.taint`](https://nextjs.org/docs/app/guides/data-security#tainting)), ale przewodnik traktuje je jako dodatkową warstwę, a nie zamiennik filtrowania.

### Funkcje nie przechodzą

Przekazanie zwykłej funkcji jako propa z Server Component do Client Component nie przejdzie, więc `onClick` zdefiniowany na serwerze nie zadziała. [Lista serializowalnych propsów](https://react.dev/reference/rsc/use-client#serializable-types) w dokumentacji Reacta dopuszcza tylko Server Functions i nie obejmuje też instancji klas ani obiektów z prototypem null. Definiuj handlery w Client Component albo przekaż Server Function oznaczoną `"use server"`, która przechodzi jako odwołanie.

### „use client” za wysoko

`"use client"` na layoucie albo stronie przenosi do grafu klienta wszystko pod spodem: każdy import i każdy komponent, który ten plik renderuje bezpośrednio. [Dokumentacja Next.js](https://nextjs.org/docs/app/getting-started/server-and-client-components#reducing-js-bundle-size) zaleca dawać dyrektywę interaktywnemu liściowi, na przykład polu wyszukiwania, a layout wokół zostawić jako Server Component.

### Komponenty zewnętrzne bez dyrektywy

Komponent z biblioteki, który używa `useState`, ale nie ma `"use client"`, wywołuje błąd, gdy renderuje go Server Component. Dokumentacja radzi, jak użyć takich [komponentów zewnętrznych](https://nextjs.org/docs/app/getting-started/server-and-client-components#third-party-components): opakuj komponent we własny plik z dyrektywą, który go reeksportuje.

## Podatności React Server Components: aktualizuj React i Next.js

Wydanie React 19.3.1 wymienia przy React Server Components jedną zmianę, [„Improve transport protocol”](https://github.com/react/react/releases/tag/v19.3.1). 14 października 2026 React opublikował krytyczny komunikat [Unauthenticated Remote Code Execution in React Server Components](https://github.com/react/react/security/advisories/GHSA-xqc2-xf4c-x3h3) (zdalne wykonanie kodu bez uwierzytelnienia). Wymienia w nim paczki `react-server-dom-webpack`, `react-server-dom-turbopack` i `react-server-dom-parcel` w wersjach od 19.0.0 do 19.3.0, z poprawkami w 19.0.9, 19.1.10, 19.2.9 i 19.3.1. Samej paczki `react` nie ma na tej liście, więc sprawdź te paczki także w lockfile. Tego samego dnia [Next.js 16.4.1](https://github.com/vercel/next.js/releases/tag/v16.4.1) naprawił trzy własne podatności. W naszym przeglądzie nowości z 18 października 2026, podlinkowanym na początku tekstu i w dalszej lekturze, jest, czego dotyczy każda z nich i którą wersję wziąć.

Demo sprawdziliśmy na Next.js 16.4.0, czyli w zakresie podatności Next.js, z React 19.3.0. Na wersjach z poprawkami jeszcze go nie uruchomiliśmy; na nich buduj własną aplikację.

## Dalsza lektura

- [Next.js: Server and Client Components](https://nextjs.org/docs/app/getting-started/server-and-client-components): Kiedy używać których, payload RSC, przeplatanie przez children i server-only.

- [Next.js: The Server and Client Boundary](https://nextjs.org/docs/app/guides/server-and-client-boundary): Gdzie działa każdy komponent, jak przechodzą kod i dane, właściciel a rodzic.

- [Next.js: Data security](https://nextjs.org/docs/app/guides/data-security): Co propsy wysyłają do przeglądarki, warstwa dostępu do danych, taint i kontrole w Server Actions.

- [React: Server Components](https://react.dev/reference/rsc/server-components): Dokumentacja: czym są Server Components, komponenty asynchroniczne i stabilność w React 19.

- [React: 'use client'](https://react.dev/reference/rsc/use-client): Jak dyrektywa oznacza moduł i jego importy oraz które propsy są serializowalne.

- [React: Unauthenticated Remote Code Execution in React Server Components](https://github.com/react/react/security/advisories/GHSA-xqc2-xf4c-x3h3): Komunikat z 14 października 2026: podatne paczki i wersje 19.x z poprawką.

- [Nowości web dev: RCE w React i Next.js, fast mode Opus 5.5](https://www.outofplace.space/pl/blog/nowosci-web-dev-2026-10-18): Nowości web dev z 11–18 października 2026: łatki React i Next.js na trzy krytyczne RCE, fast mode dla Claude Opus 5.5 bez preview i Python na Vercel w GA.
