理系大学院生のぼちぼちITノート

【初心者向け】Next.jsでお問い合わせフォームを実装する全手順|API連携とスパム対策も解説

はじめに

今回は、Next.js(App Router)で作ったブログやサイトに、安全なお問い合わせフォームを簡単に実装する全手順を、一つ一つ丁寧に解説していきます。

「お問い合わせページを作りたいけど、何から手をつければいいの?」

「フォームのデータって、どこに送ればいいの?」

「スパム対策とか、セキュリティってどうすればいいんだろう?」

この記事は、そんな風に悩んでいる初学者の方に向けて書きました。この手順通りに進めれば、コピー&ペーストだけでも、しっかり機能するお問い合わせページが完成します。

今回使う技術スタック

  • フロントエンド: Next.js (App Router)
  • バックエンド: microCMS (APIでデータ管理)
  • ホスティング: Vercel
  • スパム対策: Upstash (レートリミット)

すでにブログでmicroCMSをお使いの方なら、使い慣れた管理画面にお問い合わせが届くようになるので、特におすすめです。

【準備】まずはガワから!フォームの画面を作る

まだ機能は空っぽで構いません。ユーザーが触れるUI(ユーザーインターフェース)の部分を先に完成させましょう。

お問い合わせページの作成 (/contact)

まず、app/contact/page.tsx というファイルを作成し、お問い合わせフォームを設置するページを用意します。

import { Metadata } from 'next';
import ContactForm from '@/components/ContactForm';
import styles from './page.module.css';

export const metadata: Metadata = {
  title: 'お問い合わせ',
  description: 'お問い合わせフォームです。',
};

export default function ContactPage() {
  return (
    <div className={styles.container}>
      <h1 className={styles.title}>お問い合わせ</h1>
      <p className={styles.description}>
        ご質問やご相談など、お気軽にお問い合わせください。
      </p>
      <ContactForm />
    </div>
  );
}

ページのタイトルなどを設定し、後ほど作成する ContactForm コンポーネントを呼び出しているだけのシンプルな構成です。

フォームコンポーネントの作成

次にお問い合わせフォームの本体です。components/ContactForm/index.tsx を作成します。

'use client';

import { useState } from 'react';
import styles from './index.module.css';

export default function ContactForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [message, setMessage] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    alert('フォームが送信されました(まだ見た目だけです)');
  };

  return (
    <form className={styles.form} onSubmit={handleSubmit}>
      <div className={styles.formGroup}>
        <label htmlFor="name" className={styles.label}>お名前</label>
        <input type="text" id="name" className={styles.input} value={name} onChange={(e) => setName(e.target.value)} required />
      </div>
      <div className={styles.formGroup}>
        <label htmlFor="email" className={styles.label}>メールアドレス</label>
        <input type="email" id="email" className={styles.input} value={email} onChange={(e) => setEmail(e.target.value)} required />
      </div>
      <div className={styles.formGroup}>
        <label htmlFor="message" className={styles.label}>お問い合わせ内容</label>
        <textarea id="message" className={styles.textarea} value={message} onChange={(e) => setMessage(e.target.value)} required ></textarea>
      </div>
      <button type="submit" className={styles.button}>送信</button>
    </form>
  );
}

useState を使って、ユーザーの入力を一時的に保存しています。この時点では、送信ボタンを押してもアラートが出るだけです。

フッターからリンクを繋ぐ

最後に、サイトのフッターからこのページにアクセスできるように、components/Footer/index.tsx にリンクを追加します。

import Link from 'next/link';
import styles from './index.module.css';

export default function Footer() {
  return (
    <footer className={styles.footer}>
      <nav className={styles.nav}>
        {/* ... 他のリンク ... */}
        <Link href="/contact" className={styles.link}>
          お問い合わせ
        </Link>
      </nav>
      {/* ... */}
    </footer>
  );
}

これで、見た目上は完璧なフォームが完成し、サイト内からアクセスできるようになりました。
しかし、まだ魂が入っていません。次のステップで、このフォームに命を吹き込みましょう。

【機能】データを安全に受け取るバックエンドを構築する

ここからが本番です。入力されたデータを、安全に受け取って保存する「中身」の部分を作ります。

なぜバックエンドが必要? API Routeの役割

フォームのデータをただメールで送るだけでは、スパムの温床になったり、APIキーなどの秘密の情報がブラウザから丸見えになったりして非常に危険です。
そこで、ユーザーのブラウザとmicroCMSの間に、安全な中継役となるサーバー側のプログラム(API Route)を設置します。

ブラウザ → (安全な通信) → Next.jsのAPI Route → (秘密の鍵で通信) → microCMS

この構成にすることで、大事なAPIキーを公開することなく、安全にデータを扱うことができます。

データの保存先を準備 (microCMS APIの作成)

  1. microCMSの管理画面で「APIを作成」→「自分で決める」を選択。
  2. API名を「お問い合わせ」、エンドポイントを「contacts」に設定。
  3. APIの型は「リスト形式」を選択。
  4. APIスキーマは name (テキスト)、email (テキスト)、message (複数行テキスト) の3つを作成。必須項目にチェックをつけます。
  5. 重要: 作成後、「サービス設定」→「APIキー管理」から、POSTの権限を必ずオンにしてください。

安全な中継役を実装 (Next.js API Routeの作成)

app/api/contact/route.ts という新しいファイルを作成し、サーバーサイドで動く中継役のプログラムを記述します。

import { NextResponse, NextRequest } from 'next/server';
import { createContact } from '@/libs/microcms';

export async function POST(request: NextRequest) {
  try {
    const json = await request.json();
    if (!json.name || !json.email || !json.message) {
      return NextResponse.json({ error: '必須項目が入力されていません。' }, { status: 400 });
    }
    const data = await createContact(json);
    return NextResponse.json(data, { status: 201 });
  } catch (error) {
    return NextResponse.json({ error: 'サーバーでエラーが発生しました。' }, { status: 500 });
  }
}

microCMSクライアントとフォームを接続する

まず、libs/microcms.ts にお問い合わせデータを送信するための関数を追加します。

// ... (既存のコード) ...

export type Contact = { name: string; email: string; message: string; };

// ... (clientの初期化など) ...

export const createContact = async (data: Contact) => {
  return await client.create({ endpoint: 'contacts', content: data });
};

次に、components/ContactForm/index.tsxhandleSubmit 関数を、このAPI Routeを呼び出すように書き換えます。

// ...

const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setStatus('loading');
    setErrorMessage('');

    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, email, message }),
      });

      if (res.ok) {
        setStatus('success');
        setName('');
        setEmail('');
        setMessage('');
      } else {
        const errorData = await res.json();
        setErrorMessage(errorData.error || 'メッセージの送信に失敗しました。');
        setStatus('error');
      }
    } catch (error) {
      setErrorMessage('ネットワークエラーが発生しました。');
      setStatus('error');
    }
  };
  
// ...

これで、フォームに入力されたデータが、安全な経路を通ってmicroCMSに保存されるようになりました!

【発展】セキュリティ対策を施す

機能は完成しましたが、このままではスパム攻撃を受ける可能性が十分あります。そこで、セキュリティ対策も行いましょう。

連続投稿(スパム)を防ぐ「レートリミット」の実装

レートリミットとは、同じ人(IPアドレス)が短時間に何回もリクエストを送れないようにする仕組みです。

  1. Upstashで無料DB作成: Upstashで無料のRedisデータベースを作成し、UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKENを取得します。
  2. パッケージインストール: npm install @upstash/ratelimit @upstash/redis をターミナルで実行します。
  3. API Routeに実装: app/api/contact/route.tsの先頭に、レートリミットのチェック処理を追加します。
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '60 s'), // 60秒に5回まで
});

export async function POST(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json({ error: '1分間の送信上限を超えました。時間をおいて再度送信をお願いいたします。' }, { status: 429 });
  }
  
  // ... (以降の処理)
}

(任意) 日本語以外の問い合わせを弾くフィルタリング

私の場合、海外からのスパムメッセージが数件来ました。すぐに翻訳ができる時代ですし、基本的には日本語のみでの受付でよいと感じたので、メッセージに日本語が含まれていない場合はエラーにしました。

// ... (レートリミット処理の後)
try {
    const json = await request.json();
    // ...
    const message = json.message as string;
    const japaneseRegex = /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/;
    if (!japaneseRegex.test(message)) {
      return NextResponse.json(
        { error: 'お問い合わせは日本語でお願いいたします。Please write your inquiry in Japanese.' },
        { status: 400 }
      );
    }
    // ...
} // ...

【本番公開】Vercelに環境変数を設定する

最後に、本番環境でこのシステムを動かすための設定です。ローカルの.envファイルはVercelにアップロードされないため、Vercel側で別途設定が必要です。

  1. Vercelのプロジェクトダッシュボードを開きます。
  2. 「Settings」→「Environment Variables」に移動します。
  3. 以下のキーと値をすべて設定します。
    • MICROCMS_SERVICE_DOMAIN
    • MICROCMS_API_KEY
    • UPSTASH_REDIS_REST_URL
    • UPSTASH_REDIS_REST_TOKEN
    • BASE_URL
  4. 設定後、プロジェクトを再デプロイ(Redeploy)します。

これで、本番環境でもお問い合わせフォームが動作します!

おわりに

お疲れ様でした!

今回は、Next.jsとmicroCMSを使って、ただのフォームではなく、サーバーサイドでの処理やセキュリティ対策まで考慮した、お問い合わせページを実装する方法を解説しました。

一つ一つの手順は難しくありませんが、組み合わせることで堅牢なシステムが出来上がります。

この記事が、あなたの開発の助けになれば嬉しいです。