React Server Components и серверные действия

Базовая модель: разделение клиентского и серверного мира

React Server Components (RSC) вводят принципиально иную модель рендеринга, в которой компоненты могут выполняться либо на сервере, либо на клиенте, но при этом остаются частью единого дерева React.

Ключевая идея заключается в том, что:

  • серверные компоненты выполняются только на сервере
  • клиентские компоненты выполняются в браузере
  • данные и UI могут комбинироваться без явных REST-запросов на уровне компонентов

Серверные компоненты не попадают в бандл JavaScript, отправляемый в браузер. Это означает:

  • отсутствие клиентского кода там, где он не нужен
  • уменьшение размера bundle
  • прямой доступ к backend-ресурсам (БД, файловая система, внутренние API)

Типы компонентов: Server и Client

В RSC модель разделяет компоненты на два типа.

Серверные компоненты (Server Components)

По умолчанию все компоненты являются серверными, если не указано обратное.

Они:

  • выполняются на сервере при каждом запросе (или при статической генерации)
  • могут обращаться к базе данных напрямую
  • не имеют доступа к browser API (window, document)
  • не используют состояния React вроде useState, useEffect

Пример:

// Server Component
async function UserList() {
  const users = await db.user.findMany();

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Здесь отсутствует клиентский JavaScript, а данные загружаются напрямую.


Клиентские компоненты (Client Components)

Клиентские компоненты явно обозначаются директивой:

"use client";

Они:

  • выполняются в браузере
  • могут использовать state, эффекты и события
  • могут содержать интерактивность

Пример:

"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onCl ick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Граница между сервером и клиентом

Серверные компоненты могут импортировать клиентские, но не наоборот.

Это создаёт строгую иерархию:

  • Server → Client: допустимо
  • Client → Server: запрещено

Причина: серверный код не должен попадать в клиентский bundle.


Поток данных в React Server Components

В классической модели React данные передаются через API-запросы:

  1. клиент рендерится
  2. делает fetch
  3. получает JSON
  4. рендерит UI

В RSC модель выглядит иначе:

  1. сервер рендерит дерево компонентов
  2. выполняет запросы к данным внутри компонентов
  3. сериализует результат в специальный поток (Flight format)
  4. клиент получает уже готовую структуру UI

Это уменьшает количество сетевых запросов и упрощает архитектуру.


Серверные действия (Server Actions)

Server Actions позволяют выполнять серверные функции напрямую из компонентов без явного API-слоя.

Они обозначаются директивой:

"use server";

Пример серверного действия

"use server";

import db from "@/lib/db";

export async function createUser(formData) {
  const name = formData.get("name");

  await db.user.create({
    data: { name }
  });
}

Использование в форме

import { createUser } from "./actions";

export default function UserForm() {
  return (
    <form action={createUser}>
      <input name="name" placeholder="Name" />
      <button type="submit">Create</button>
    </form>
  );
}

Форма отправляется напрямую на серверную функцию без ручного fetch.


Отличие Server Actions от API Routes

До появления Server Actions стандартная схема выглядела так:

  • frontend → fetch → API route → backend logic → database

Теперь:

  • frontend → Server Action → database

Разница:

  • исчезает слой REST/GraphQL для простых операций
  • уменьшается количество шаблонного кода
  • логика ближе к UI

Ограничения Server Actions

Несмотря на удобство, существуют важные ограничения:

  • нельзя использовать внутри client-only логики напрямую без передачи через props
  • серверное действие всегда выполняется на сервере
  • результат должен быть сериализуемым
  • нельзя возвращать функции или классы

Связка Server Components и Server Actions

Обе технологии работают совместно:

  • Server Components отвечают за получение и отображение данных
  • Server Actions отвечают за мутации данных

Пример комбинированного подхода:

// server component
import { createPost } from "./actions";

export default async function Page() {
  const posts = await db.post.findMany();

  return (
    <div>
      <form action={createPost}>
        <input name="title" />
        <button type="submit">Add</button>
      </form>

      <ul>
        {posts.map(p => (
          <li key={p.id}>{p.title}</li>
        ))}
      </ul>
    </div>
  );
}

Передача данных между сервером и клиентом

RSC использует сериализацию дерева компонентов, а не JSON API.

Это означает:

  • props могут передаваться от серверных компонентов к клиентским
  • данные проходят через границу автоматически
  • нет необходимости вручную сериализовать ответы

Пример:

// Server Component
import ClientButton from "./ClientButton";

export default function Page() {
  const time = new Date().toISOString();

  return <ClientButton time={time} />;
}
"use client";

export default function ClientButton({ time }) {
  return <button>{time}</button>;
}

Интерактивность и гидрация

Клиентские компоненты всё ещё требуют гидрации:

  • сервер отдает HTML
  • клиент “оживляет” интерактивные части

Но отличие RSC:

  • не весь UI гидрируется
  • только клиентские части получают JS

Работа с данными без useEffect

В традиционном React:

  • данные загружаются через useEffect
  • требуется управление состоянием загрузки

В RSC:

  • данные загружаются на сервере
  • клиент получает уже готовый результат

Это убирает необходимость:

  • loading states для базового UI
  • лишних запросов после mount

Паттерны архитектуры в RSC

1. Server-first подход

UI строится вокруг серверных компонентов:

  • страницы = серверные компоненты
  • данные = загрузка внутри компонентов
  • интерактивность = точечные client islands

2. Client Islands

Клиентские компоненты используются как острова интерактивности:

  • кнопки
  • формы
  • drag-and-drop
  • состояние UI

3. Colocation логики

Логика и UI располагаются рядом:

  • Server Action рядом с формой
  • запрос данных внутри страницы
  • отсутствие отдельного API слоя

Ошибки и типичные проблемы

Использование browser API в Server Component

window.location // ошибка

Сервер не имеет доступа к браузеру.


Импорт server code в client component

"use client";

import db from "@/lib/db"; // ошибка

Client bundle не может содержать серверные модули.


Несериализуемые данные

Нельзя передавать:

  • функции
  • классы
  • сложные объекты с методами

Производительность

RSC и Server Actions дают следующие оптимизации:

  • уменьшение JavaScript bundle
  • снижение гидрации
  • уменьшение количества API запросов
  • более эффективный SSR

Особенно заметно в:

  • dashboard приложениях
  • CMS
  • e-commerce
  • внутренних админках

Итоговая модель исполнения

Система React с Server Components работает как конвейер:

  1. сервер строит дерево компонентов
  2. выполняет серверные компоненты
  3. сериализует результат
  4. клиент получает минимальный JS
  5. интерактивные части активируются отдельно
  6. Server Actions обрабатывают мутации без API слоя

Взаимодействие с Next.js

На практике RSC и Server Actions тесно связаны с Next.js App Router:

  • app/ directory использует Server Components по умолчанию
  • Server Actions интегрированы в routing и forms
  • layout и page компоненты являются серверными

Это делает архитектуру приложения более декларативной и централизованной.