React Server Components: как они работают и зачем нужны
Продвинутое3 минРедакция Innovialabs
Коротко
Server Components выполняются только на сервере и не попадают в JavaScript-бандл браузера; интерактивные части выносятся в Client Components с "use client", а между ними передаются только сериализуемые данные.
Что такое Server Components
React Server Components (RSC) — компоненты, которые выполняются только на сервере. Их код не отправляется в браузер: клиент получает уже готовый результат рендеринга в специальном формате, а React встраивает его в страницу.
Что это даёт:
- меньше JavaScript в браузере — тяжёлые библиотеки (парсер Markdown, форматирование дат, SDK базы данных) остаются на сервере;
- данные рядом с источником — компонент может напрямую обратиться к базе или внутреннему API;
- секреты в безопасности — ключи и токены не попадают в клиентский бандл.
Важно: RSC — это не то же самое, что классический SSR. SSR превращает компоненты в HTML для первого показа, но потом весь их код всё равно загружается в браузер. Server Components в браузер не загружаются вовсе.
Граница сервер/клиент и "use client"
В фреймворках с поддержкой RSC (например, App Router в Next.js) компоненты по умолчанию серверные. Чтобы сделать компонент клиентским, в начале файла ставят директиву:
"use client";
import { useState } from "react";
export function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? "♥" : "♡"}</button>;
}
Ключевые правила:
"use client"отмечает точку входа на клиент: этот файл и всё, что он импортирует, попадает в бандл.- Клиентским компонентам доступны состояние, эффекты, обработчики событий и браузерные API.
- Серверный компонент может рендерить клиентский. Обратное — импортировать серверный компонент внутрь клиентского — нельзя.
- Но серверный компонент можно передать в клиентский через
childrenили другой prop:
// серверный компонент
<Tabs>
<ProductDescription id={id} />
</Tabs>
Здесь Tabs — клиентский, а ProductDescription остаётся серверным.
Правила сериализации
Всё, что серверный компонент передаёт клиентскому через props, должно быть сериализуемым.
| Можно передавать | Нельзя передавать |
|---|---|
| строки, числа, boolean, null | обычные функции и обработчики |
| простые объекты и массивы | экземпляры классов |
| Date, Map, Set | символы, созданные не через Symbol.for |
| JSX (серверные компоненты как children) | объекты с циклическими ссылками |
| Promise | соединения с БД, сокеты |
| Server Actions (функции с "use server") |
Частая ошибка — передать onClick из серверного компонента. Решение: перенести обработчик внутрь клиентского компонента или использовать Server Action.
Загрузка данных
Серверный компонент может быть асинхронным:
export default async function OrdersPage() {
const orders = await db.order.findMany({ take: 20 });
return <OrdersTable orders={orders} />;
}
Практические паттерны:
- Загружайте данные там, где они нужны, а не в одном корневом компоненте. Одинаковые запросы в рамках рендера можно дедуплицировать через
cacheиз React. - Запускайте независимые запросы параллельно через
Promise.all, иначе получите «водопад» из последовательных ожиданий. - Используйте Suspense, чтобы медленная часть не блокировала всю страницу: быстрые блоки покажутся сразу, медленные — по мере готовности.
- Для изменения данных — Server Actions, а не fetch к собственному API.
Типичные ошибки
"use client"в корневом лейауте. Всё дерево становится клиентским, и выгода RSC пропадает. Держите клиентские компоненты маленькими и на «листьях».- Импорт серверного кода в клиентский файл. Библиотека для БД попадёт в бандл или сборка упадёт. Пакет
server-onlyпомогает поймать это на этапе сборки. - Ожидание, что серверный компонент перерендерится от клика. Он рендерится по запросу, а не от состояния в браузере. Для обновления нужна навигация,
router.refresh()или ревалидация. - Передача лишних данных в props. Всё, что уходит в клиентский компонент, видно в ответе сервера. Не отдавайте целые записи из базы с внутренними полями.
- Использование контекста в серверных компонентах.
createContextи провайдеры работают только на клиенте.