Серверные действия
Fluent React: Build Fast, Performant, and Intuitive Web Applications
Глубокое погружение во внутреннее устройство React: JSX и продвинутые паттерны, виртуальный DOM и реконциляция, серверный рендеринг, конкурентный режим и серверные компоненты.
«use server»: функции сервера, вызываемые с клиента
Серверные компоненты — не единственная новинка. В паре с ними работает директива «use server», превращающая обычную функцию в server action (в переводе книги — «серверное действие»).
- Любая асинхронная функция может начинаться с «use server» — сигнал React и компоновщику: вызывать можно с клиента, выполнять — только на сервере.
- Вызов server action на клиенте — это сетевой запрос с сериализованной копией всех аргументов.
- Возвращаемое значение сериализуется и приходит обратно клиенту.
async function requestUsername(formData) {
"use server";
const username = formData.get("username");
// выполняется только на сервере
}Стоп. Нам же говорили: функции не сериализуются?
Так и есть — и правило не нарушено: по сети едет не функция, а ссылка на неё. Та же механика, что module reference у клиентских компонентов из доклада 9.2 — только в обратную сторону.
Тело остаётся на сервере
Компоновщик видит «use server» и не кладёт код функции в клиентский пакет. Вместо него клиент получает ссылку: ID модуля + имя функции.
Импорт превращается в стаб
Поэтому клиентский компонент может импортировать server action и звать её как обычную async-функцию — под капотом импортируется не код, а «адрес».
Вызов — это POST
Стаб отправляет на сервер запрос: ID функции + сериализованные аргументы. Сервер находит функцию по ID, выполняет и возвращает сериализованный результат.
Пометить функцию — или сразу весь файл
Директива в теле функции
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 |
| Форма с ошибками и pending | onSubmit, preventDefault, useState на ошибки и загрузку | useActionState: состояние формы приходит из результата экшена, pending бесплатно |
| Мгновенный отклик UI | Руками копить «оптимистичное» состояние и откатывать | useOptimistic поверх server action — откат при ошибке автоматом |
| Логин и чекаут на слабой сети | Кнопка мертва, пока не доехал бандл | Нативный POST до гидратации — критичный путь живой сразу |
Стоит запомнить только это
- Server action — асинхронная функция с «use server»: вызывается с клиента, выполняется на сервере.
- Аргументы и результат сериализуются и ездят по сети — как пропсы у серверных компонентов.
- В формах server action передаётся в action, React сам подставляет FormData — и форма работает даже до загрузки JS.
- Вне форм server actions дружат с useTransition: индикаторы загрузки, оптимистичные обновления, обработка ошибок.
- Это открытые endpoint'ы и API уровня фреймворков — использовать через Next.js и аналоги, не забывая о безопасности.
Что далее
Антон Помазков
Антон Помазков
Артём Никифоров
Артём Никифоров
Артём Никифоров