Resend と Astro Actions を使用したフォームを作成する

Resend と Astro Actions を使用したフォームを作成する方法を実装しながらまとめてみました。

はじめに

こちらの記事はResend と Astro Actions を使用したフォームを作成する方法を実装しながらまとめてみました。 誤った内容を書いている可能性があるため、もしお気づきの際にはご指摘いただけると幸いです。

デモ

今回のデモサイトは以下になります。

Resendとは

Resendは開発者向けのメール送信 API サービスです。
SDKとAPIトークンを使ってメール送信の処理を書くことができます。
インストールは以下のコマンドで行います。

Terminal window
npm add resend

Astro Actionsとは

Astro Actionsは型安全なバックエンド関数を定義し、呼び出すことができるAstroの機能です。
アクションは、src/actions/index.tsを作成することでAstroがサーバーアクションとして読み込んでくれます。
データのフェッチやバリデーション(Zod)を自動的に行うことができます。

基本的な使い方は、公式のドキュメントがわかりやすいです。

公式ドキュメント - Astro Actions

React Hook Form とは

React Hook FormはReactでフォームを作成するためのライブラリです。
不要な再レンダリングを防ぎ、フォームの入力値やエラーの管理を行うことができます。
インストールは以下のコマンドで行います。

Terminal window
npm add react-hook-form
公式ドキュメント - React Hook Form

実装

実装について、詳細にみてみます。

デモサイト

全体の処理の流れ

  1. [ユーザー入力]
  2. [React Hook Form] クライアントバリデーション (mode: ‘onBlur’)
  3. [確認画面] getValues() で入力値を表示
  4. [FormData] React の state → FormData に変換
  5. [Astro Actions] actions.send(formData) → サーバーへ送信
  6. [Zod バリデーション] サーバーサイドで再バリデーション
  7. [Resend API] メール送信(管理者通知 + ユーザー自動返信)
  8. [完了画面 or エラー画面]

useFormの処理

src/components/module/FormContainer.tsx
const {
register,
handleSubmit,
reset,
getValues,
formState: { errors },
} = useForm<FormValues>({
mode: "onBlur",
defaultValues: {
name: "",
email: "",
address: "",
tel: "",
select: "",
message: "",
privacy: false,
},
});

今回処理に使用しているuseFormの引数と返り値について、以下のようになっています。

useFormの引数

引数説明
modeバリデーションのモードを指定する。onBluronChangeonSubmitonTouchedallが指定でき、それぞれのイベントが発生した時にバリデーションを行う。
defaultValuesフォームの初期値を指定する。デフォルトでは空のオブジェクトが設定されている。

useFormの返り値

変数/関数役割
registerinput要素をフォームに紐付ける
handleSubmitバリデーションが成功した時に送信処理を実行する
resetフォーム値を初期化(または指定値にリセット)する
getValues現在のフォーム値を取得する
formState.errorsバリデーションエラーを参照する
公式ドキュメント - React Hook Form

Astro Actionsの処理

Astro Actionsで記載する、SSRで行うAPI処理は以下のようになります。 行なっていることとしては、サーバーのアクションをsendとして定義し、データー形式をformとして受け取り、zodを使用してバリデーションを行っています。 バリデーションが成功すれば、handlerでResendのAPIを使用してメール送信の処理を行います。

src/actions/index.ts
import { ActionError, defineAction } from "astro:actions";
import { z } from "astro:schema";
import { RESEND_API_TOKEN } from "astro:env/server";
import { Resend } from "resend";
export const server = {
send: defineAction({
accept: "form",
input: z.object({
name: z.string(),
email: z.string().email(),
postCode: z.string().optional(),
address: z.string().optional(),
tel: z
.string()
.regex(/^[\d-]+$/)
.optional(),
select: z.string().optional(),
message: z.string(),
privacy: z.string().transform((val) => val === "on"),
}),
handler: async (input) => {
const resend = new Resend(RESEND_API_TOKEN);
const html = `<table>
<tr>
<td>名前</td>
<td>${input.name}</td>
</tr>
<tr>
<td>メールアドレス</td>
<td>${input.email}</td>
</tr>
<tr>
<td>住所</td>
<td>${input.address}</td>
</tr>
<tr>
<td>電話番号</td>
<td>${input.tel}</td>
</tr>
<tr>
<td>選択</td>
<td>${input.select}</td>
</tr>
<tr>
<td>お問い合わせ内容</td>
<td>${input.message}</td>
</tr>
<tr>
<td>プライバシーに同意して送信してください。</td>
<td>${input.privacy ? "同意" : "不同意"}</td>
</tr>
</table>`;
const { data, error } = await resend.batch.send([
{
from: "Acme <onboarding@resend.dev>",
to: ["delivered@resend.dev"],
subject: `${input.name} 様からお問い合わせがありました。`,
html,
},
{
from: "Acme <onboarding@resend.dev>",
to: ["delivered@resend.dev"],
subject: "お問い合わせありがとうございました。",
html: `
<p>${input.name} 様</p>
<p>お問い合わせいただきありがとうございます。</p>
<p>以下の内容で承りました。担当者より折り返しご連絡いたします。</p>
${html}
`,
},
]);
if (error) {
throw new ActionError({
code: "BAD_REQUEST",
message: error.message,
});
}
return data;
},
}),
};

SSRでの処理は以下のように記述します。 今回はサーバーへの送信処理を書きたかったので、sendという名前で定義しています。
また、フォームの送信テストにResendで提供されているテスト用アドレスを使用しています。

export const server = {
myActions: defineAction({}),
};

defineActionの引数ついて、以下のようになっています。

defineActionの引数

引数説明
accept受け取るデータの形式を指定する。formjsontextbinaryが指定でき、それぞれの形式に応じてデータを受け取る。
inputデータの型を指定する。z.objectz.arrayz.stringz.numberz.booleanz.datez.enumz.customが指定でき、それぞれの型に応じてデータを受け取る。
handlerデータの処理を行う関数を指定する。
公式ドキュメント - Astro Actions 公式ドキュメント - Resend API Reference

注意

環境変数の読み込みを、Astro Actions内で記述するときは、通常の環境変数の読み込み方法とは異なり、astro:env/serverを使用して読み込む必要があります。

公式ドキュメント - Astro Actions

入力画面のステートの管理

  • useStateで入力、確認、完了、エラーの状態管理
  • onSubmit関数の進捗に応じて、状態を変更
type FormStepStatus = "input" | "confirm" | "success" | "error";
const [status, setStatus] = useState<FormStepStatus>("input");
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!formData) return;
try {
const fd = new FormData();
Object.entries(formData).forEach(([key, value]) => {
if (key === "privacy" && value) {
fd.append(key, "on");
} else if (value) {
fd.append(key, String(value));
}
});
const { error } = await actions.send(fd);
if (error) {
throw new Error(error.message);
}
setStatus("success");
reset();
} catch (error) {
setStatus("error");
console.error(error);
}
};

AstroActionsを使うとenvの読み方が変わる

サーバー処理で環境変数を使用する場合は、通常の呼び出し方とは異なるため注意が必要です。
より安全に環境変数を呼び出せるように、アストロ側でサーバー側で使用可能なものを制限できたり、型安全に扱えるようにスキーマを定義できます。 詳しくはAstroの公式ドキュメントがわかりやすいです。

一覧に戻る