React
Server Components: введение
Классическое React-приложение работает так: браузер скачивает гигантский бандл JS, выполняет его, тот делает запрос к API, ждёт ответа и только потом рисует контент. Server Components ломают эту схему. Компонент выполняется на сервере, ходит в базу напрямую, отдаёт уже готовую разметку, и на клиент не уезжает ни строчки его кода. Библиотека форматирования дат на 40 КБ, нужная только для одного блока, остаётся на сервере. Клиент получает результат, а не инструмент.
- React 19 (декабрь 2024): Server Components стабилизированы как часть официального API React
- Next.js App Router: в нём каждый компонент по умолчанию серверный, клиентским он становится только по директиве 'use client'
- Vercel, Shopify Hydrogen: коммерческие платформы строят витрины на RSC ради нулевого клиентского JS для статичных частей
- React Docs: документация описывает RSC, границу сериализации и правила перехода между сервером и клиентом
- TanStack Start и React Router 7: фреймворки вне Next.js тоже внедряют поддержку Server Components
Предварительные знания
- Хук use() и Suspense: чтение промиса в рендере и приостановка компонента
- Понимание разницы между кодом, который выполняется на сервере, и кодом в браузере
- Базовое знание props и того, как данные передаются от родителя к ребёнку
Как сервер вернулся в модель рендеринга React
В декабре 2020 команда React показала демо и RFC под названием Server Components. Идея звучала радикально: пусть часть дерева компонентов выполняется только на сервере и никогда не попадает в клиентский бандл. Это не то же самое, что классический SSR, где сервер рендерит HTML, но потом весь JS всё равно отправляется на клиент для гидрации. В RSC код серверного компонента не отправляется вовсе. Несколько лет ушло на проработку протокола сериализации и интеграцию с фреймворками. Next.js сделал RSC дефолтом своего App Router, а в React 19 (декабрь 2024) API стабилизировали. Так сервер, изгнанный из фронтенда эпохой SPA, вернулся как первоклассная часть модели.
Что значит 'выполняется только на сервере'
Server Component - это компонент, чей код выполняется на сервере во время запроса и никогда не отправляется в браузер. Результат его работы - не HTML-строка и не JS, а особое сериализованное описание UI, которое React на клиенте встраивает в дерево. Поскольку код остаётся на сервере, тяжёлые зависимости (парсеры markdown, библиотеки дат, SDK базы) не попадают в клиентский бандл.
Два момента отличают такой компонент от обычного. Во-первых, он может быть async и await-ить данные прямо в теле - не нужен отдельный API-эндпоинт и клиентский fetch. Во-вторых, у него есть прямой доступ к серверным ресурсам: переменные окружения с секретами, файловая система, подключение к базе. Этот код физически не достижим из браузера, поэтому секреты не утекают.
| Возможность | Server Component | Client Component |
|---|---|---|
| Код едет в браузер | Нет | Да |
| Прямой доступ к БД и секретам | Да | Нет |
| useState, useEffect, обработчики | Нет | Да |
| Может быть async/await в теле | Да | Нет |
В серверном компоненте недоступны хуки состояния, эффекты и обработчики событий: onClick, useState, useEffect там не работают. Причина проста - на сервере нет интерактивности и нет жизненного цикла в браузере. Всё интерактивное выносится в клиентские компоненты.
Почему Server Component может обращаться к базе данных прямо в теле, а обычный клиентский компонент нет?
Граница сериализации
Серверные и клиентские компоненты живут в одном дереве, но в разных мирах: один на сервере, другой в браузере. Между ними проходит граница сериализации. Серверный компонент может рендерить клиентский и передавать ему props, но только такие, которые можно сериализовать и переслать по сети: строки, числа, массивы, простые объекты, и даже промисы. А вот функции, классы, обработчики событий через границу не проходят - их нельзя превратить в данные.
Передача промиса через границу - мощный приём. Серверный компонент стартует запрос, но не ждёт его, а отдаёт сам промис клиентскому компоненту. Тот читает его через use() и приостанавливается под Suspense, пока данные летят. Это связывает RSC с уроком про use(): сервер создаёт стабильный промис, клиент его дочитывает.
Нельзя передать обработчик из серверного компонента в клиентский: <Button onClick={handleClick} />, где handleClick объявлен в серверном компоненте, не сериализуется и упадёт. Обработчики событий должны жить внутри клиентского компонента, а из сервера в него передаются только данные.
Что можно передать через props от серверного компонента к клиентскому?
Директива 'use client' и где проходит граница
По умолчанию в RSC-окружении компонент серверный. Чтобы сделать его клиентским, в самое начало файла ставится директива 'use client'. Это помечает модуль и всё, что он импортирует, как клиентское: код поедет в браузер, там заработают useState, useEffect, обработчики событий. Директива - это объявление границы: 'отсюда и глубже по импортам начинается клиент'.
Граница ставится как можно глубже. Распространённая ошибка - повесить 'use client' на корневой layout и утянуть в клиент всё приложение, потеряв весь смысл RSC. Правильнее держать серверным максимум дерева, а 'use client' добавлять листьям, которым реально нужна интерактивность: кнопкам, формам, дропдаунам. Серверный компонент при этом может свободно рендерить клиентские как детей.
RSC - это не классический SSR, хотя их часто путают. При SSR сервер рендерит HTML, но затем весь JS компонентов всё равно едет в браузер для гидрации. В RSC код серверного компонента не отправляется вообще, и гидрировать там нечего. Эти две техники работают вместе: SSR отдаёт первый HTML, RSC решает, чей код вообще попадёт на клиент.
Что делает директива 'use client' в начале файла?
Связь с другими темами
RSC - фундамент серверной части React. Что рядом:
- Server Functions — Серверный код, вызываемый из клиента для мутаций; продолжение серверной модели в сторону действий
- Next.js App Router — Фреймворк, где Server Components - дефолт, а файловая система задаёт маршруты
- Хук use() — Серверный компонент создаёт промис, клиентский дочитывает его через use()
Итог
- Server Component выполняется только на сервере: его код не попадает в клиентский бандл, поэтому клиентский JS сокращается
- Серверный компонент имеет прямой доступ к базе, файловой системе и секретам - можно ходить в БД прямо в теле компонента без отдельного API-слоя
- Между сервером и клиентом проходит граница сериализации: через props можно передавать сериализуемые данные и промисы, но не функции и не обработчики событий
- Директива 'use client' в начале файла помечает компонент и его импорт-поддерево как клиентское - там работают хуки состояния и обработчики событий
- RSC - это не классический SSR: при SSR весь JS всё равно едет на клиент для гидрации, а код серверного компонента не отправляется вообще
Связанные уроки
- rc-31-server-functions — Server Functions ('use server') - способ вызывать серверный код из клиента; естественное продолжение серверного рендеринга
- rc-35-nextjs-app-router — App Router в Next.js - дефолтный способ запускать Server Components в проде в 2026 году
- rc-29-use-hook — Серверный компонент часто создаёт промис и отдаёт его клиентскому, где данные дочитываются через use()