Глава 9 Серверные компоненты React

Серверные действия

Книга
React. К вершинам мастерства

Fluent React: Build Fast, Performant, and Intuitive Web Applications

Глубокое погружение во внутреннее устройство React: JSX и продвинутые паттерны, виртуальный DOM и реконциляция, серверный рендеринг, конкурентный режим и серверные компоненты.

Глава 9 · Server Actions

«use server»: функции сервера, вызываемые с клиента

Серверные компоненты — не единственная новинка. В паре с ними работает директива «use server», превращающая обычную функцию в server action (в переводе книги — «серверное действие»).

  • Любая асинхронная функция может начинаться с «use server» — сигнал React и компоновщику: вызывать можно с клиента, выполнять — только на сервере.
  • Вызов server action на клиенте — это сетевой запрос с сериализованной копией всех аргументов.
  • Возвращаемое значение сериализуется и приходит обратно клиенту.
async function requestUsername(formData) {
  "use server";
  const username = formData.get("username");
  // выполняется только на сервере
}
Зачем две директивы? Дефолт в мире RSC — серверный: модуль без директив живёт в серверном графе. «use client» — дверь с сервера на клиент («отсюда начинается клиентский пакет»), «use server» — дверь с клиента на сервер («эту функцию можно дёрнуть снаружи»). Они не симметричны: первая помечает модули, вторая — функции-endpoint'ы.
Как это работает

Стоп. Нам же говорили: функции не сериализуются?

Так и есть — и правило не нарушено: по сети едет не функция, а ссылка на неё. Та же механика, что module reference у клиентских компонентов из доклада 9.2 — только в обратную сторону.

01

Тело остаётся на сервере

Компоновщик видит «use server» и не кладёт код функции в клиентский пакет. Вместо него клиент получает ссылку: ID модуля + имя функции.

02

Импорт превращается в стаб

Поэтому клиентский компонент может импортировать server action и звать её как обычную async-функцию — под капотом импортируется не код, а «адрес».

03

Вызов — это POST

Стаб отправляет на сервер запрос: ID функции + сериализованные аргументы. Сервер находит функцию по ID, выполняет и возвращает сериализованный результат.

Красивая симметрия с правилами из 9.3: серверный компонент импортировать в клиентский код нельзя (утащит код в пакет), а server action — можно (уедет только адрес). И по той же причине гидратация — работа клиента: HTML не может привезти функции-обработчики.
Синтаксис

Пометить функцию — или сразу весь файл

Директива в теле функции

async function requestUsername(formData) {
  "use server";
  // ...
}

Точечно: в server action превращается одна конкретная функция.

Директива в начале файла

"use server"; // первая строка файла

export async function likePost(id) { /* ... */ }
export async function follow(userId) { /* ... */ }

Весь экспорт файла — server actions. Их можно использовать где угодно, включая импорт в клиентский код.

Формы и мутации

Форма, которая работает до загрузки JavaScript

React даёт первоклассные примитивы для форм: server action передаётся прямо в action, и при отправке формы происходит сетевой запрос к серверной функции.

// App.js
async function requestUsername(formData) {
  "use server";
  const username = formData.get("username");
  // ...
}

export default function App() {
  return (
    <form action={requestUsername}>
      <input type="text" name="username" />
      <button type="submit">Request</button>
    </form>
  );
}
  • Фаза 1 — приехал HTML. Форма уже на экране и уже рабочая: отправку формы браузер умеет сам, без JavaScript. Фреймворк зашил в action URL с ID нашей функции — сабмит уйдёт обычным POST, сервер выполнит функцию и вернёт новую страницу.
  • Фаза 2 — догрузился JS-бандл (React + клиентские компоненты) и оживил страницу. Теперь отправку перехватывает React: тот же POST, но через fetch — без перезагрузки, с pending и оптимистичными обновлениями.
  • Итог: форма рабочая в обеих фазах. На быстрой сети разницы не видно; на слабой — логин и чекаут живут за секунды до того, как доедет бандл.
За пределами форм

Server action + useTransition: лайк без формы

Server actions можно вызывать из любого места клиентского кода. Вызов в переходе даёт индикатор загрузки, оптимистичные обновления и обработку ошибок.

"use client";
import incrementLike from "./actions";
import { useState, useTransition } from "react";

function LikeButton() {
  const [isPending, startTransition] = useTransition();
  const [likeCount, setLikeCount] = useState(0);

  const onClick = () => {
    startTransition(async () => {
      // ждём promise, чтобы прочитать результат
      const currentCount = await incrementLike();
      setLikeCount(currentCount);
    });
  };

  return (
    <>
      <p>Total Likes: {likeCount}</p>
      <button onClick={onClick} disabled={isPending}>Like</button>
    </>
  );
}
  • Напомню про useTransition (глава 7): startTransition помечает обновление как несрочное — «делай в фоне, интерфейс не замораживай». С async-функцией внутри он ещё и следит за ней: пока она не завершилась, isPending === true.
  • Здесь это даёт бесплатный индикатор запроса: кнопка выключена, пока летит POST, — без единого useState под загрузку. Сам вызов — обычный await: результат читаем и кладём в состояние.
  • Слабое место кода: счётчик обновится только после ответа сервера — на медленной сети лайк «залипает».
  • Причём тут оптимистичный UI: поверх той же транзиции работает useOptimistic — показываем «+1» мгновенно, не дожидаясь сервера; если экшен упал, React сам откатит значение.
Осторожно

Это открытые конечные точки сервера

Помним про безопасность

Открытые — не по выбору, а по факту: раз браузер должен уметь вызвать функцию, компоновщик генерирует для неё публичный HTTP-endpoint. Импорт выглядит приватным, но дёрнуть endpoint можно curl'ом напрямую — с любыми аргументами, минуя вашу форму и UI. Поэтому внутри — проверка прав и валидация входа, как в любом публичном API.

Это API для фреймворков

В ванильном React работа с директивами громоздка и требует немалой настройки. На практике server actions предназначены для библиотек и фреймворков — Next.js и подобных.

Тем не менее это мощная функция, открывающая множество интересных вариантов использования — от форм до произвольных мутаций.
Кейсы

Когда это нужно: типовые флоу

Книга отвечает «как», но не «когда». Вот что server actions заменяют в повседневной работе — и чем обвязывается UX в React 19.

СценарийРаньше (SPA)Теперь
Мутация данных: создать, обновить, удалитьТранспорт пишем сами: роут + формат JSON на бэке, fetch по URL-строке на клиенте, типы синхронизируем руками, кэш инвалидируем самиСетевой запрос тот же, но транспорт генерирует компоновщик: импортируешь функцию и вызываешь её — с типами; revalidatePath сам перерисовывает данные. RPC вместо REST
Форма с ошибками и pendingonSubmit, preventDefault, useState на ошибки и загрузкуuseActionState: состояние формы приходит из результата экшена, pending бесплатно
Мгновенный отклик UIРуками копить «оптимистичное» состояние и откатыватьuseOptimistic поверх server action — откат при ошибке автоматом
Логин и чекаут на слабой сетиКнопка мертва, пока не доехал бандлНативный POST до гидратации — критичный путь живой сразу
Типовой флоу в Next.js сегодня: <form action={serverAction}> → мутация БД в экшене → revalidatePath / redirect → RSC перерисовывает страницу с новыми данными. Клиентского кода — ноль строк.
Главные выводы

Стоит запомнить только это

  • Server action — асинхронная функция с «use server»: вызывается с клиента, выполняется на сервере.
  • Аргументы и результат сериализуются и ездят по сети — как пропсы у серверных компонентов.
  • В формах server action передаётся в action, React сам подставляет FormData — и форма работает даже до загрузки JS.
  • Вне форм server actions дружат с useTransition: индикаторы загрузки, оптимистичные обновления, обработка ошибок.
  • Это открытые endpoint'ы и API уровня фреймворков — использовать через Next.js и аналоги, не забывая о безопасности.

Что далее

Преимущества Пройдено
Антон Помазков
Антон Помазков
Артём Никифоров
Артём Никифоров