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 ComponentClient 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()
Server Components: введение

0

1

Войти