Astro Actions: форма с валидацией Zod
Определение Action с accept: form, валидация полей через Zod (astro/zod), вызов из HTML-формы и Astro.getActionResult().
Как использовать
- Добавь Action в src/actions/index.ts с accept: form и input через z.object().
- На странице используй action={actions.contact} в форме и Astro.getActionResult() для отображения результата или ошибок.
- Убедись, что страница с формой рендерится on-demand (prerender: false или output server/hybrid).
При обработке форм в Astro в режиме SSR или hybrid часто нужна типобезопасная валидация на сервере и единообразный вывод ошибок по полям. Без этого данные приходят «как есть», дублирование правил на клиенте и сервере приводит к рассинхрону, а пользователь видит общее сообщение об ошибке вместо привязки к конкретному полю. Симптомы: форма отправляется с некорректными данными, ошибки валидации не отображаются у полей, либо страница с формой отдаёт 404 или не вызывает action при статической сборке. Ниже — готовое решение через Astro Actions и Zod: одна схема валидации, вывод ошибок по полям и проверка запроса через curl.
Решение
Обработка форм в Astro 5 через Actions: типобезопасность и валидация на сервере. Zod импортируется из astro/zod, Actions — из astro:actions. Для вызова action через атрибут формы страница должна быть on-demand (export const prerender = false или output: "server" / "hybrid").
Определение Action (docs.astro.build — Actions):
// src/actions/index.ts
import { defineAction } from "astro:actions";
import { z } from "astro/zod";
export const server = {
contact: defineAction({
accept: "form",
input: z.object({
name: z.string().min(2, "Имя минимум 2 символа"),
email: z.string().email("Некорректный email"),
message: z.string().min(10, "Сообщение минимум 10 символов"),
}),
handler: async (input) => {
const { name, email, message } = input;
// отправка email, запись в БД и т.д.
return {
success: true,
message: "Спасибо за обращение! Мы свяжемся с вами.",
};
},
}),
};
Форма и результат на странице (Call actions from HTML form action):
---
// src/pages/contacts.astro
import { actions } from "astro:actions";
const result = Astro.getActionResult(actions.contact);
---
{result?.data?.success && (
<p class="success">{result.data.message}</p>
)}
{result?.error && (
<p class="error">{result.error.message}</p>
)}
<form method="POST" action={actions.contact}>
<label for="name">Имя</label>
<input type="text" id="name" name="name" required />
{result?.error?.fields?.name && (
<span class="field-error">
{Array.isArray(result.error.fields.name) ? result.error.fields.name[0] : result.error.fields.name}
</span>
)}
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<label for="message">Сообщение</label>
<textarea id="message" name="message" rows="5" required></textarea>
<button type="submit">Отправить</button>
</form>
Вызов Action из клиентского скрипта (прогрессивное улучшение):
import { actions, isInputError } from "astro:actions";
const form = document.querySelector("form");
form?.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(form);
const { data, error } = await actions.contact(formData);
if (isInputError(error)) {
// ошибки полей в error.fields
return;
}
if (error) {
// серверная ошибка
return;
}
if (data) {
// успех: data.message и т.д.
}
});
- Все Actions экспортируются из объекта
serverвsrc/actions/index.ts. - При
accept: "form"handler получает уже распарсенный и провалидированный объект (не сырой FormData). - Ошибки валидации содержат
error.fields(сообщения по имени поля); для проверки используйтеisInputError(error).
Проверка
Убедиться, что форма и валидация работают:
- Запуск dev-сервера (страница с формой должна быть on-demand):
pnpm dev
Открой страницу с формой (например /contacts), отправь форму с пустым или неверным email — под полем должно появиться сообщение вида «Некорректный email».
- Диагностика ответа action через curl (проверка, что сервер возвращает структуру ошибки):
curl -X POST "http://localhost:4321/contacts" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=A&email=invalid&message=short"
В ответе ожидаются данные в формате, который Astro использует для getActionResult (например, редирект с cookie или тело с полем error и error.fields в зависимости от реализации). Успешная отправка с валидными данными должна вернуть редирект или страницу с сообщением об успехе.
- Проверка типа ошибки в коде: если обрабатываешь action в клиентском скрипте, используй
isInputError(error)— тогда можно безопасно выводитьerror.fieldsпо полям.
Типичные ошибки
- Страница с формой отдаёт 404 или action не срабатывает — страница собрана статически. Добавь в frontmatter страницы
export const prerender = falseили используйoutput: "server"/"hybrid"вastro.config.mjs. - Ошибки валидации не показываются у полей — не выводишь
result?.error?.fields?.name(и остальные поля). Убедись, что имена полей в форме совпадают с ключами вz.object(). - В клиентском скрипте падает при обращении к error.fields — сначала проверь
isInputError(error); у серверных ошибок структура другая.
Где применять
- Разработка: локальный
pnpm devс режимом server или hybrid. - Продакшен: хостинг с поддержкой Node (Vercel, Netlify, свой сервер) и выбранным адаптером деплоя. Страница с формой не должна быть пререндерена.
Связанные сниппеты: API Route (endpoint) с динамическим путём, Подключение React в островке, Статический вывод по умолчанию.
⚠️ Страница с формой, вызывающей action через action={actions.contact}, должна быть отрендерена on-demand. В статическом режиме добавь в frontmatter страницы export const prerender = false или используй output: "server" / "hybrid".