Серверное действие и проверка Zod
Проверяем реальные значения на сервере и возвращаем ошибки полей.
На этой странице
Для Next.js 16.3.8 (App Router) и React 19.3.0, Zod 4.6.5. Обновлено .
Данные на сервер приходят из браузера. Даже если в коде указан тип string, пользователь может прислать значение другого типа или строку неподходящего формата. Zod описывает правила и проверяет реальные значения во время выполнения программы.
Путь заявки
Поля формы → FormData → серверное действие → схема Zod → ошибки или проверенные данные → сообщение в форме.
TypeScript проверяет ваш код при разработке. Zod проверяет присланные значения при выполнении.
Установите в учебном проекте:
bun add zodСхема: одно место для правил
import { z } from "zod";
export const registrationSchema = z.object({
name: z
.string({ error: "Введите имя." })
.trim()
.min(2, "Имя должно содержать хотя бы два символа.")
.max(80, "Имя слишком длинное."),
email: z
.string({ error: "Введите email." })
.trim()
.max(254, "Адрес email слишком длинный.")
.pipe(z.email({ error: "Проверьте адрес email." })),
date: z.iso.date({ error: "Выберите корректную дату." }),
comment: z
.string({ error: "Комментарий должен быть текстом." })
.trim()
.max(500, "Комментарий не должен превышать 500 символов."),
});
export type Registration = z.infer<typeof registrationSchema>;
export type RegistrationField = keyof Registration;
export type RegistrationErrors = Partial<Record<RegistrationField, string[]>>;
export type RegistrationState = {
status: "idle" | "error" | "success";
message: string;
fieldErrors: RegistrationErrors;
};
export const initialRegistrationState: RegistrationState = {
status: "idle",
message: "",
fieldErrors: {},
};z.object задает устройство объекта. trim убирает пробелы по краям, min и max ограничивают длину. pipe передает обработанную строку следующей проверке. Поэтому email проверяется уже без случайных крайних пробелов.
z.iso.date проверяет дату вида 2026-10-14, включая корректность календарной даты. Эта схема не проверяет, что дата будущая или что мероприятие действительно существует. Для таких правил нужны данные афиши и отдельная серверная проверка. z.email проверяет формат, но не существование почтового ящика.
z.infer выводит тип из схемы. keyof Registration дает имена её полей, Record связывает имя поля со значением, Partial позволяет не добавлять в объект поля, у которых нет ошибок. Если эти записи незнакомы, вернитесь к повторному использованию типов.
Комментарий необязателен для пользователя: пустая строка допускается. Но поле comment присутствует в нашей форме, поэтому схема ожидает строку, а не отсутствующий ключ.
use server: функция выполняется на сервере
"use server";
import { registrationSchema, type RegistrationState } from "@/lib/registration";
import { z } from "zod";
export async function checkRegistration(
_previousState: RegistrationState,
formData: FormData,
): Promise<RegistrationState> {
const result = registrationSchema.safeParse({
name: formData.get("name"),
email: formData.get("email"),
date: formData.get("date"),
comment: formData.get("comment"),
});
if (!result.success) {
return {
status: "error",
message: "Проверьте отмеченные поля.",
fieldErrors: z.flattenError(result.error).fieldErrors,
};
}
return {
status: "success",
message: `${result.data.name}, данные проверены. Заявка пока не сохранена.`,
fieldErrors: {},
};
}"use server" в начале файла помечает его экспортируемые асинхронные функции как серверные функции. Когда такая функция используется для отправки формы, её называют Server Action, или серверным действием.
Обычному серверному компоненту директива "use server" не нужна. Она также не заменяет проверку прав доступа: если действие доступно только определенным пользователям, проверяйте их права внутри функции.
safeParse возвращает один из двух результатов. При success: true проверенные данные лежат в data. При false есть error. z.flattenError группирует сообщения по полям плоской формы. Для глубоко вложенной формы потребуется другой способ представления ошибок.
При использовании useActionState первый аргумент содержит ответ на предыдущую отправку формы. Здесь он не нужен, поэтому назван _previousState. Второй аргумент – текущая FormData. Обычная форма без этого хука передает FormData первым аргументом; учитывайте этот порядок аргументов.
Что этот пример делает и чего ещё не хватает
Сообщение об успехе означает, что поля прошли проверку. В действии нет базы данных, отправки письма или бронирования. Следующая статья подключает его к интерфейсу.
Для настоящей записи сервер дополнительно проверяет:
- Кто отправил запрос и разрешено ли ему это действие.
- Существует ли выбранное мероприятие и доступно ли оно.
- Не была ли эта заявка уже сохранена.
- Можно ли безопасно сохранить данные при одновременных заявках.
Отключенная кнопка помогает пользователю, но не защищает сервер от повторного запроса. Скрытое поле и значение цены от клиента тоже нельзя считать надежными. Все эти проверки выполняются на сервере.
Попробуйте: передайте в safeParse пробелы вместо имени, неверную дату и null вместо email. Затем проверьте правильный объект. Сравните обычную ошибку ввода с неожиданным сбоем источника: первая возвращается в форму, второй требует обработки ошибки.
Документация: формы Next.js, use server, Zod, ошибки Zod.