TS
#astro#actions#zod#forms#validation#server

Astro Actions: форма с валидацией Zod

Определение Action с accept: form, валидация полей через Zod (astro/zod), вызов из HTML-формы и Astro.getActionResult().

Как использовать

  1. Скопируйте нужный фрагмент кода.
  2. Вставьте в свой проект и при необходимости измените под задачу.
  3. Проверьте зависимости и окружение (версии, переменные).

Обработка форм в 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 и т.д.
  }
});

Usage:

  • Все Actions экспортируются из объекта server в src/actions/index.ts.
  • При accept: "form" handler получает уже распарсенный и провалидированный объект (не сырой FormData).
  • Ошибки валидации содержат error.fields (сообщения по имени поля); для проверки используйте isInputError(error).

Notes:

⚠️ Страница с формой, вызывающей action через action={actions.contact}, должна быть отрендерена on-demand. В статическом режиме добавьте в frontmatter страницы export const prerender = false или используйте output: "server" / "hybrid".