フォームの入力チェック、ブラウザ側だけで安心していませんか?
AIに「入力フォームを作って」と頼むと、こんなコードが返ってくることがあります。
// Before: ブラウザ側の入力チェックだけに頼っている
<form action={createUser}>
<input name="email" type="email" required />
<input name="age" type="number" min="18" required />
<button type="submit">登録</button>
</form>
// app/actions.ts
"use server";
export async function createUser(formData: FormData) {
const email = formData.get("email") as string;
const age = Number(formData.get("age"));
await supabase.from("users").insert({ email, age });
}
ブラウザで実際にフォームを操作すると、type="email"やrequiredのおかげで、空欄のまま送信しようとしたり、メール欄に変な文字列を入れようとすると、ちゃんと止めてくれます。この動きを見て「バリデーションは実装できた」と判断してしまうことがあります。
【何が起きるか】
このrequiredやtype="email"といった指定は、あくまでブラウザが表示するフォーム上でのチェックです。ブラウザの開発者ツールでこのinput要素の属性を消してしまえば、チェックは効かなくなります。もっと単純に、ブラウザを経由せず、createUserが呼ばれる先のエンドポイントに直接リクエストを送ることもできます。
つまり、ageに-5のような値や、emailに不正な文字列が入った状態でも、サーバー側のcreateUserはそのままデータベースへの保存を実行してしまいます。ブラウザでの動作確認だけをしていると、この抜けには気づけません。
【直し方】
// app/actions.ts(修正後)
"use server";
export async function createUser(formData: FormData) {
const email = formData.get("email") as string;
const age = Number(formData.get("age"));
if (!email || !email.includes("@")) {
return { error: "メールアドレスの形式が正しくありません" };
}
if (!age || age < 18) {
return { error: "年齢は18以上を入力してください" };
}
await supabase.from("users").insert({ email, age });
return { success: true };
}
サーバー側(Server Action や API Route)の中でも、同じ内容のチェックをもう一度書きます。ブラウザ側のrequiredやtypeは「入力する人への案内・親切さ」のためのものであり、実際にデータを守るチェックはサーバー側で行う、という役割分担で考えると整理しやすいです。(なお、returnしたエラーメッセージを画面に表示するには、別途useActionStateなどの受け皿が必要です。今回はデータを守る部分に絞って紹介しています。)
【AIに確認するときの聞き方】
フォームの実装を書いてもらったら、「このチェックはブラウザ側だけですか?サーバー側でも同じ内容を検証していますか?」と一言確認するだけで、この種の抜けはその場で気づけます。
面談なしで、今日から始められます
この講座は 未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を全20セッションで体系化した教材付きプランです。無料相談を挟まず、申し込んだその日から教材で学習を始められます。AIが学習パートナーになって何度でも質問でき、つまずいた所だけチャットで直接サポートします。
プログラミングの知識だけでなく、AIへの指示の出し方や、AIが出してきたコードを自分の言葉で確認する目も、教材を進める中で自然に身についていきます。
- 今日から始める(教材完全版+月5,500円・チャット質問し放題・いつでも解約OK)→ https://menta.work/plan/20251?ref=menta-knowledge
- いきなりは不安な方へ:無料の教材体験版もあります(最初の数セッション分)→ プラン詳細をご覧ください

