Что ломается при сериализации: секреты стабильных Server Actions и Server Components

Вы отправляете форму или рендерите компонент, а в ответ получаете непонятную ошибку «Error: Only plain objects, and a few built-ins, can be passed to Server Actions». Или ваш продакшен-сервер внезапно падает с переполнением памяти, а причина кроется в одном, казалось бы, безобидном пропсе, переданном в Server Component. Эти проблемы возникают из-за одного корня — нарушений в процессе сериализации данных между сервером и клиентом в современном стеке React. Их сложно отладить, они подрывают стабильность приложения и тратят часы вашего времени.

Я помогу вам системно разобраться, какие объекты являются «несериализуемыми», почему архитектура Next.js и React Server Components накладывает эти ограничения, и — самое главное — как проектировать код, чтобы обходить эти подводные камни. Вы получите не просто список «нельзя», а понимание механики и практические паттерны для написания устойчивого кода в 2026 году и далее.

Сердце системы: зачем нужна сериализация и как она работает

Server Actions и Server Components обмениваются данными не через живое соединение, а через сериализованные сообщения. Представьте, что вы отправляете телеграмму: вы не можете вложить в конверт живую лягушку или текущий поток воды, только их текстовое описание. Стек RSC использует специальный формат для передачи виртуального DOM и результатов вычислений. Этот формат основан на JSON, но с расширениями для конкретных типов React. Ключевое ограничение: весь обмен между клиентом и сервером, включая аргументы функций и возвращаемые значения, должен быть сериализуемым.

Это не просто прихоть, а фундаментальное требование для предсказуемости, безопасности и производительности. Несериализуемые данные могут содержать скрытые зависимости, замыкания на гигантские объекты или чувствительную логику, которая не должна покидать сервер. Когда вы нарушаете это правило, вы получаете либо явную ошибку, либо тихую утечку памяти, либо нерабочий UI.

Глубокое погружение: алгоритм сериализации RSC

Процесс не просто использует JSON.stringify. React применяет рекурсивный обход объекта. При обнаружении функции, символа или экземпляра класса он пытается найти для них зарегистрированный сериализатор. Если такового нет — возникает ошибка. Для Date объектов используется строковое представление по стандарту ISO. Для Map и Set — преобразование в массив пар [key, value]. Важно понимать, что сериализуется не сам объект, а его значение на момент вызова. Динамические методы и контекст (this) теряются.

Чёрный список: что никогда нельзя передавать через границу сервер-клиент

Вот основные категории опасных объектов, которые вы должны отслеживать в своём коде.

  • Экземпляры классов (кроме встроенных). new DatabaseConnection(), new MyService(), new ThirdPartySDK(). Они содержат методы и закрытое состояние, которое невозможно корректно восстановить на другой стороне.
  • Функции (включая колбэки и методы). Передача onClick={() => {...}} из Server Component в клиентский компонент — частая ошибка. Функции замыкаются на область видимости сервера, их выполнение на клиенте бессмысленно и опасно.
  • Сложные прототипы и циклические ссылки. Объекты, имеющие ссылки друг на друга по кругу, приводят к бесконечной рекурсии при сериализации и падению процесса Node.js.
  • Символы (Symbol) и приватные поля класса. Они уникальны в рамках одного runtime и теряют свой смысл при десериализации.
  • Потоки (Streams), сокеты, дескрипторы файлов. Это живые ресурсы операционной системы, их нельзя описать в JSON.

Реалистичный кейс: форма загрузки файла с валидацией

Допустим, вы создаёте Server Action uploadDocument, которая принимает файл и проводит его проверку с помощью сторонней библиотеки document-validator. Ошибка: вы импортируете и создаёте экземпляр валидатора new Validator(config) прямо в теле действия. При каждом вызове это создаёт новый несериализуемый объект. Даже если ошибка не возникает явно, вы можете столкнуться с утечкой памяти, потому что движок будет пытаться сохранять контекст валидатора для отладки. Решение: выносите инициализацию таких тяжёлых или несериализуемых зависимостей в верхний уровень модуля, используя кэширование или синглтон-паттерн в рамках серверного runtime.

Серые зоны: объекты, которые сериализуются с оговорками

С некоторыми типами данных нужно быть особенно внимательным.

Date. Он сериализуется в строку ISO формата, но десериализуется обратно в строку, а не в объект Date, если не использовать кастомные парсеры. Не полагайтесь на методы .getFullYear() у объекта, пришедшего с сервера.

Map и Set. Превращаются в обычные массивы. Их специфичные методы (.get(), .has()) теряются. Если вам критична их структура, преобразуйте их в массив пар Array.from(myMap.entries()) явно.

BigInt. Не сериализуется стандартным JSON. В стеке RSC часто требуется специальная обработка или преобразование в строку.

Поля с undefined. В отличие от null, они могут быть опущены при сериализации, что иногда ломает ожидания формы на клиенте. Явно используйте null для обозначения пустых значений.

Фундаментальная ошибка новичка и её исправление

Самая распространённая и разрушительная ошибка — прямая передача в Server Action объекта, полученного из базы данных ORM (например, Prisma или Sequelize).

Плохой код:

// Server Action
export async function updateUser(userId: string, formData: FormData) {
  const user = await db.user.findUnique({ where: { id: userId } });
  // Попытка передать `user` в клиентский компонент
  return user; // ОПАСНО: user — это экземпляр модели Prisma!
}

Почему это ломается? Объект модели содержит не только данные, но и методы (.update(), .delete()), приватный контекст соединения с БД и, возможно, циклические ссылки. Его сериализация либо вызовет ошибку, либо, что хуже, отправит на клиент чувствительные методы и метаданные.

Исправление: Всегда выбирайте только простые, сериализуемые поля. Используйте .select() или явно маппируйте объект.

// Правильный код
export async function getUserData(userId: string) {
  const user = await db.user.findUnique({
    where: { id: userId },
    select: { id: true, name: true, email: true, avatarUrl: true } // Только примитивы
  });
  return user; // Теперь это plain object
}

Note: Если вам нужно передать сложные вычисления, реализуйте их полностью на сервере и отправляйте готовый результат. Клиент должен получать данные для отображения, а не логику для их обработки.

Вопрос и ответ: типичный сценарий с классом

Вопрос: У меня есть утилитарный класс PriceCalculator с методами applyDiscount() и convertCurrency(). Мне нужно использовать его и на сервере для расчётов, и передать результат на клиент. Как правильно это организовать?

Ответ: Класс должен жить только на сервере. В Server Action вы создаёте его экземпляр, выполняете методы и получаете результат в виде простого объекта. Передаёте на клиент только конечные числовые значения или простые структуры.

// Серверная сторона
const calculator = new PriceCalculator();
const finalPrice = calculator.calculate(basePrice, selectedOptions);
// Возвращаем только сериализуемый результат
return { finalPrice, currency: 'RUB' };

// Клиентская сторона просто показывает эти данные.

Практический аудит: как проверить свой код прямо сейчас

Чтобы внедрить эти знания, выполните конкретные действия сегодня.

  1. Воспользуйтесь встроенным в Next.js 15+ валидатором типов для Server Actions. Убедитесь, что ваши входные и выходные типы — это только примитивы, простые объекты и массивы.
  2. Запустите поиск по кодовой базе: найдите все вызовы use server и экспортируемые async-функции. Проверьте их возвращаемые значения и параметры.
  3. Проверьте пропсы ваших Server Components. Убедитесь, что вы не передаёте в них функции, экземпляры сервисов или объекты БД.
  4. Создайте скрипт или используйте ESLint плагин для автоматического поиска опасных паттернов, таких как new ClassName() внутри Server Actions.
  5. Тестируйте не только «счастливый путь», но и передачу null, undefined и вложенных объектов в ваши Server Actions. Используйте строгие схемы валидации, например, с помощью Zod.

Глубокое понимание сериализации — это не академическое упражнение, а необходимое условие для создания быстрых, безопасных и стабильных полностековых приложений. Основной тренд 2026 года — это дальнейшее разделение ответственности: сервер отвечает за логику и данные, клиент — за интерактивность и представление. Нарушая границу сериализации, вы ломаете эту архитектуру. Ваша следующая задача — взять ваш наиболее сложный Server Action и провести по нему пятишаговый аудит, описанный выше. Вы сразу найдёте и устраните скрытые точки отказа.

Picture of Роман

Роман

автор-эксперт

Популярные статьи по теме

Бесплатные курсы для детей от государства и крупных платформ

Бесплатные курсы для детей от государства и крупных платформ

Бесплатные курсы для детей: только госпрограммы и тренды 2026-2027 Родители сегодня ищут бесплатные курсы для детей не от бедности, а ...
Обучение школьников геймдизайну на Unity и Roblox Studio

Обучение школьников геймдизайну на Unity и Roblox Studio

Как за 12 месяцев сделать из школьника уверенного геймдизайнера Вы смотрите, как ваш ребёнок часами сидит в играх, и беспокоитесь ...
Профориентация школьников 14–17 лет: тесты и курсы для выбора будущей профессии

Профориентация школьников 14–17 лет: тесты и курсы для выбора будущей профессии

Как помочь подростку с профориентацией в 2026 году Вы смотрите на своего подростка и видите, как он часами листает ленту, ...
Программирование для детей 7–12 лет: Scratch, Minecraft и Python — обзор школ

Программирование для детей 7–12 лет: Scratch, Minecraft и Python — обзор школ

Как выбрать школу программирования для ребёнка 7–12 лет в 2026 году Вы смотрите десятки сайтов школ, обещающих обучить ребёнка с ...
Стратегический менеджмент: российские кейсы для предпринимателей

Стратегический менеджмент: российские кейсы для предпринимателей

Стратегия компании: как адаптировать под реалии 2026 года Большинство российских предпринимателей подходят к стратегическому менеджменту как к формальному ритуалу. Раз ...
Операционный директор (COO) на удаленке: сертификация и MBA онлайн

Операционный директор (COO) на удаленке: сертификация и MBA онлайн

Андрей Комаров – операционный директор (COO) международной IT-компании с распределённой командой из 12 стран. С 2019 года управляет процессами удалённо ...