Пошук уроків, статей та іншого контенту
Розберете архітектуру React Server Components, їхнє виконання на сервері та передачу результату клієнту.
React Server Components (RSC) — це компоненти React, які виконуються на сервері, а їхній результат передається клієнту.
На відміну від звичайних клієнтських компонентів, код Server Component:
не потрапляє до JavaScript-бандла браузера;
може отримувати дані безпосередньо на сервері;
не має доступу до window, document та інших браузерних API;
не обробляє події користувача безпосередньо;
може містити асинхронний код на верхньому рівні компонента.
Важливо: Server Component — це не просто компонент, який один раз відрендерили на сервері. Він є частиною спеціальної архітектури React, де сервер і браузер виконують різні частини інтерфейсу.
У традиційному клієнтському React компоненти зазвичай виконуються в браузері. Щоб відобразити сторінку з даними, браузер може завантажити:
JavaScript-код застосунку.
Код компонентів.
Дані з API.
Логіку побудови інтерфейсу.
Server Components переносять частину цієї роботи на сервер:
Сервер отримує дані.
React формує спеціальний результат рендерингу.
Клієнт отримує цей результат.
Браузер завантажує JavaScript лише для клієнтських компонентів, яким потрібна інтерактивність.
Це дає кілька переваг:
менший JavaScript-бандл у браузері;
менше роботи для пристрою користувача;
можливість звертатися до серверних джерел даних без проміжного клієнтського запиту;
зручніше розділення статичної та інтерактивної частин інтерфейсу.
У застосунку з RSC існують два основні типи компонентів.
Server Component:
виконується на сервері;
може читати дані з бази даних або файлової системи через серверний код;
може бути асинхронним;
не використовує клієнтські React-хуки, наприклад useState або useEffect;
не може мати обробники подій на кшталт onClick.
Приклад Server Component:
// app/products/page.tsx
type Product = {
id: number;
name: string;
};
async function getProducts(): Promise<Product[]> {
const response = await fetch("https://api.example.com/products");
if (!response.ok) {
throw new Error("Не вдалося завантажити товари");
}
return response.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<main>
<h1>Товари</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</main>
);
}У середовищі, яке підтримує React Server Components, цей компонент виконується на сервері. Браузеру не потрібно отримувати код функції getProducts або виконувати її самостійно.
У реальному застосунку адреса
https://api.example.com/productsмає бути замінена на доступне API. Сам принцип прикладу полягає в тому, що завантаження даних відбувається на сервері.
Client Component потрібен тоді, коли компонент має взаємодіяти з браузером або реагувати на дії користувача.
До таких випадків належать:
стан через useState;
ефекти через useEffect;
обробники подій onClick, onChange тощо;
браузерні API;
інтерактивні елементи інтерфейсу.
У Next.js та інших середовищах, які використовують RSC, клієнтський компонент позначають директивою "use client" на початку файлу:
// app/components/Counter.tsx
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
Натиснуто: {count}
</button>
);
}Директива "use client" не означає, що компонент потрібно вручну запускати в браузері. Вона повідомляє збирачу та React, що цей файл є початком клієнтської частини дерева.
Server Component може імпортувати Client Component:
// app/page.tsx
import Counter from "./components/Counter";
export default function HomePage() {
return (
<main>
<h1>Головна сторінка</h1>
<Counter />
</main>
);
}У цьому прикладі:
HomePage є Server Component;
Counter є Client Component;
сервер відрендерить структуру сторінки;
браузер отримає JavaScript для Counter;
після завантаження клієнтський компонент стане інтерактивним.
Це типовий підхід: сторінка та статичний вміст залишаються серверними, а невелика частина з інтерактивністю працює на клієнті.
Server Component не передає браузеру повний JavaScript-код компонента для виконання. Замість цього React формує спеціальний результат, який описує дерево React-елементів і межі між серверними та клієнтськими компонентами.
Цей результат часто називають RSC Payload.
У спрощеному вигляді потік можна уявити так:
Server Component
|
| виконується на сервері
v
RSC Payload
|
| передається клієнту
v
React у браузері
|
| підключає Client Components
v
Інтерактивний інтерфейсRSC Payload містить інформацію, потрібну клієнту, щоб:
побудувати дерево елементів;
вставити результат серверних компонентів у сторінку;
підключити потрібні клієнтські компоненти;
передати їм дозволені властивості.
Формат цього результату є внутрішнім механізмом React. Розробнику зазвичай не потрібно створювати або обробляти RSC Payload вручну — це робить фреймворк або серверна інтеграція React.
Нижче наведено приклад структури для застосунку з React Server Components. Такий формат використовується, зокрема, в Next.js App Router.
// app/page.tsx
import LikeButton from "./components/LikeButton";
type Article = {
title: string;
text: string;
};
async function getArticle(): Promise<Article> {
// Серверний компонент може завантажувати дані під час виконання на сервері.
return {
title: "React Server Components",
text: "Частина інтерфейсу може бути побудована на сервері.",
};
}
export default async function Page() {
const article = await getArticle();
return (
<main>
<article>
<h1>{article.title}</h1>
<p>{article.text}</p>
</article>
<LikeButton />
</main>
);
}// app/components/LikeButton.tsx
"use client";
import { useState } from "react";
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button type="button" onClick={() => setLiked(!liked)}>
{liked ? "Подобається" : "Вподобати"}
</button>
);
}Під час обробки сторінки:
Сервер виконує Page.
Сервер виконує getArticle.
Сервер формує результат для заголовка та тексту статті.
React бачить, що LikeButton є Client Component.
Браузер отримує результат серверної частини та JavaScript для LikeButton.
Після завантаження JavaScript кнопка реагує на натискання.
При цьому getArticle не виконується повторно в браузері, а useState працює саме в браузері.
Server Component не може використовувати useState:
// Цей компонент некоректний як Server Component.
import { useState } from "react";
export default function Form() {
const [value, setValue] = useState("");
return <input value={value} onChange={(event) => setValue(event.target.value)} />;
}Якщо компоненту потрібен стан, його потрібно зробити Client Component:
"use client";
import { useState } from "react";
export default function Form() {
const [value, setValue] = useState("");
return (
<input
value={value}
onChange={(event) => setValue(event.target.value)}
/>
);
}У Server Component недоступні:
window;
document;
localStorage;
navigator;
події браузера.
Ці об’єкти існують лише в середовищі браузера. Якщо вони потрібні, логіку потрібно перенести до Client Component.
Server Component не може напряму обробляти натискання:
// Некоректний Server Component.
export default function Button() {
return <button onClick={() => console.log("Натиснуто")}>Натиснути</button>;
}Причина полягає в тому, що функція-обробник не може просто передаватися з сервера до браузера як звичайне значення. Для інтерактивної кнопки використовуйте Client Component.
Server Component може передати дані до Client Component через props:
// app/page.tsx
import UserGreeting from "./components/UserGreeting";
export default function Page() {
return <UserGreeting name="Олена" />;
}// app/components/UserGreeting.tsx
"use client";
type UserGreetingProps = {
name: string;
};
export default function UserGreeting({ name }: UserGreetingProps) {
return <p>Вітаємо, {name}!</p>;
}Значення між сервером і клієнтом мають бути сумісними з передаванням через RSC. Найпростіші безпечні приклади — це:
рядки;
числа;
логічні значення;
масиви;
об’єкти з такими значеннями;
null.
Не слід передавати як звичайні властивості:
функції;
екземпляри класів;
серверні підключення;
об’єкти, які неможливо коректно передати клієнту.
Найчастіше дерево має таку структуру:
Server Page
├── Server Header
├── Server Article
└── Client CommentFormСерверна частина відповідає за:
отримання даних;
побудову статичного вмісту;
доступ до серверних ресурсів.
Клієнтська частина відповідає за:
введення даних;
локальний стан;
натискання кнопок;
реакцію на дії користувача.
Server Component може передати дочірній Client Component як звичайний елемент або через props. Головне — чітко визначити, де проходить межа клієнтської логіки.
Ці поняття пов’язані, але не є однаковими.
Серверний рендеринг описує спосіб отримання HTML на сервері перед відправленням сторінки браузеру.
React Server Components описують, де виконується компонент і який результат React передає між сервером та клієнтом.
Застосунок може використовувати Server Components разом із серверним рендерингом HTML. У такому випадку користувач швидше отримує початкову сторінку, а клієнтські компоненти після завантаження JavaScript додають інтерактивність.
Отже:
SSR — це стратегія формування початкової відповіді;
RSC — це модель виконання та передачі React-компонентів;
Client Components відповідають за поведінку в браузері.
Перед написанням компонента поставте собі кілька запитань:
Чи потрібен useState або інший клієнтський хук?
Чи використовує компонент window, document або localStorage?
Чи має він обробники onClick, onChange або інших подій?
Чи потрібна інтерактивність без перезавантаження сторінки?
Чи можна отримати та підготувати дані на сервері?
Якщо на перші чотири запитання відповідь «ні», компонент часто можна залишити Server Component. Якщо потрібна інтерактивність — виділіть невелику частину дерева в Client Component.
"use client" імпортом"use client" — це не імпорт і не виклик функції. Це спеціальна директива, яка має бути на початку файлу:
"use client";
import { useState } from "react";Не розміщуйте її після імпортів або всередині компонента.
Іноді "use client" додають до кореневого компонента лише тому, що одна кнопка має стан. У результаті весь піддеревний код може стати частиною клієнтської збірки.
Краще винести інтерактивність в окремий невеликий компонент:
Server Page
└── Client Buttonзамість:
Client Page
├── Header
├── Article
└── ButtonТакий підхід не є звичайним способом передавання події з Server Component до Client Component:
// Не слід передавати функцію як звичайний prop через серверно-клієнтську межу.
<InteractiveButton onClick={() => console.log("Натиснуто")} />Клієнтський компонент має містити клієнтську логіку обробки події всередині себе.
Якщо Server Component отримав дані з бази даних, це не означає, що з’єднання з базою передається клієнту. На клієнт передається лише результат, який потрібен для інтерфейсу.
Це одна з важливих переваг Server Components: серверні деталі залишаються на сервері.
React Server Components виконуються на сервері.
Їхній код не потрібно надсилати браузеру як клієнтський JavaScript.
Сервер може отримувати дані та формувати частину дерева React.
Результат передається клієнту у вигляді RSC Payload.
Client Components потрібні для стану, ефектів, браузерних API та обробників подій.
Директива "use client" позначає межу клієнтської частини.
Server Component може містити Client Component.
Інтерактивність краще ізолювати в невеликих клієнтських компонентах.
Server Components і SSR — пов’язані, але різні поняття.