🔧 Nuxt.js × Firebase で作る！無料で実装できるお問い合わせフォーム＆スパム対策 🚀

以下の手順では、Nuxt.js と Firebase（Firestore や Cloud Functions を併用する場合もあり）のみを利用してお問い合わせフォームを作成する方法と、無料で実装できるスパム対策（ハニーポットや Google reCAPTCHA の利用例）をご紹介します。以下はあくまで一例ですが、基本的な流れとコード例を交えて詳しく解説します。

## 1. Nuxt.js プロジェクトの作成と Firebase の初期設定

- **Nuxt.js プロジェクトの作成**  
  ターミナルで以下のコマンドを実行して新規プロジェクトを作成します。

  ```bash
  npx create-nuxt-app contact-form-demo
  cd contact-form-demo
  ```

- **Firebase プロジェクトの作成**  
  Firebase コンソールから新しいプロジェクトを作成し、Web アプリとして Firebase SDK の設定情報（apiKey, authDomain, projectId など）を入手します。  
  ※お問い合わせ内容の記録方法として Firestore を使うか、Cloud Functions 経由でメール送信を行うかはプロジェクトの要件に合わせて選択してください。

- **Firebase の依存パッケージインストール**  
  Nuxt プロジェクト内で Firebase SDK を利用するために以下のコマンドを実行します。

  ```bash
  npm install firebase
  ```

## 2. Firebase の初期化と Nuxt での設定

- プロジェクトルートに Firebase の設定ファイル（たとえば `firebaseConfig.js`）を作成し、以下のように初期化します。

  ```javascript
  // firebaseConfig.js
  import { initializeApp } from "firebase/app";
  import { getFirestore } from "firebase/firestore";

  const firebaseConfig = {
    apiKey: "YOUR_API_KEY",
    authDomain: "your-project.firebaseapp.com",
    projectId: "your-project-id",
    storageBucket: "your-project.appspot.com",
    messagingSenderId: "YOUR_SENDER_ID",
    appId: "YOUR_APP_ID"
  };

  const app = initializeApp(firebaseConfig);
  const db = getFirestore(app);

  export { db };
  ```

- 必要に応じて、Nuxt のプラグインとして登録し、グローバルに使えるようにしてもよいです（例えば、`plugins/firebase.js` を作成し、nuxt.config.js で登録）。

## 3. お問い合わせフォームの作成

- **ページコンポーネントの作成**  
  例として、`pages/contact.vue` を作成し、以下のようなフォームを実装します。

  ```html
  <template>
    <div class="contact-container">
      <h1>お問い合わせフォーム</h1>
      <form @submit.prevent="submitForm">
        <!-- ハニーポット用の隠しフィールド -->
        <input type="text" name="website" v-model="honeypot" style="display: none;" />

        <div>
          <label for="name">お名前</label>
          <input type="text" id="name" v-model="form.name" required />
        </div>
        <div>
          <label for="email">メールアドレス</label>
          <input type="email" id="email" v-model="form.email" required />
        </div>
        <div>
          <label for="message">お問い合わせ内容</label>
          <textarea id="message" v-model="form.message" required></textarea>
        </div>
        <!-- オプション：reCAPTCHA 用の div （後述） -->
        <div id="recaptcha-container"></div>
        <button type="submit">送信</button>
      </form>
      <p>{{ statusMessage }}</p>
    </div>
  </template>

  <script>
  // Firestore にデータを保存する例
  import { collection, addDoc } from "firebase/firestore";
  import { db } from "~/firebaseConfig"; // プラグイン化している場合は context 経由にしてもよい

  export default {
    data() {
      return {
        form: {
          name: "",
          email: "",
          message: ""
        },
        // ハニーポットフィールド（通常のユーザーは入力しないはず）
        honeypot: "",
        statusMessage: ""
      };
    },
    methods: {
      async submitForm() {
        // ① ハニーポットチェック：通常のユーザーなら空のはず
        if (this.honeypot) {
          this.statusMessage = "不正な送信と判断しました。";
          return;
        }

        // ② オプション：reCAPTCHA のトークンを取得（後述）
        // const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' });
        // this.form.recaptchaToken = token;

        // ③ Firestore へ送信、または Cloud Functions 経由でメール送信
        try {
          await addDoc(collection(db, "contactSubmissions"), {
            name: this.form.name,
            email: this.form.email,
            message: this.form.message,
            timestamp: new Date().toISOString()
          });
          this.statusMessage = "送信が完了しました。";
          this.form = { name: "", email: "", message: "" };
        } catch (error) {
          console.error("Error submitting form:", error);
          this.statusMessage = "送信エラーが発生しました。";
        }
      }
    },
    mounted() {
      // ④ もし reCAPTCHA を利用する場合はスクリプトを読み込んで初期設定をする（v3 または v2）
      // 以下は reCAPTCHA v3 の例：
      // const recaptchaScript = document.createElement('script');
      // recaptchaScript.src = `https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY`;
      // document.head.appendChild(recaptchaScript);
    }
  };
  </script>

  <style scoped>
  .contact-container {
    max-width: 600px;
    margin: 0 auto;
  }
  </style>
  ```

- この例では、フォーム送信時にまず隠しハニーポットフィールド（`honeypot`）の値を確認しています。通常ユーザーはこのフィールドに何も入力しないため、値が入っている場合はスパムと判断します。[4]

## 4. 無料のスパム対策の追加方法

スパム対策として、以下の2つの手法が無料で実装可能です。

### 4.1. ハニーポットフィールド
- すでに上記例のように、ユーザーには見えない隠しフィールド（`<input type="text" name="website" …>`）を追加します。  
- サーバー（もしくは Firestore への書き込み前）で、このフィールドが空であるかチェックするだけで簡単な対策となります。

### 4.2. Google reCAPTCHA の利用（v3 または v2）
- **設定手順**
  1. [Google reCAPTCHA](https://www.google.com/recaptcha) でサイトを登録し、サイトキーとシークレットキーを取得します。
  2. フロントエンド（上記の `mounted()` 内など）で reCAPTCHA のスクリプトを読み込み、フォーム送信時に `grecaptcha.execute` を利用してトークンを取得します。
  3. 取得したトークンをフォームデータに加え、Firebase Cloud Functions 経由で送信内容の検証を行います。Cloud Functions 内で reCAPTCHA の API にリクエストを送り、検証結果（success フラグなど）が true であるか確認してから処理を進めます。
- なお、reCAPTCHA の利用自体は無料であり、ハニーポットとの併用でも十分な効果が期待できます。[11]

## 5. Firebase Cloud Functions を利用したメール送信（オプション）

もし、Firestore に保存するだけでなくメール送信も行いたい場合は、Firebase Cloud Functions と nodemailer を組み合わせる方法があります。以下は簡単な例です。

- **Cloud Functions 側のコード例（functions/index.js）**

  ```javascript
  const functions = require("firebase-functions");
  const nodemailer = require("nodemailer");

  // Gmail を例とした SMTP 設定（環境変数は Firebase の functions:config:set コマンドで設定）
  const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
      user: functions.config().gmail.email,
      pass: functions.config().gmail.password
    }
  });

  exports.sendContactEmail = functions.https.onRequest(async (req, res) => {
    if (req.method !== "POST") {
      return res.status(405).send("Allowed only POST");
    }
    // reCAPTCHA の検証もここで追加可能
    const { name, email, message } = req.body;

    // ハニーポットの値確認（必須の場合）
    if (req.body.website && req.body.website.trim() !== "") {
      return res.status(400).send("Spam detected");
    }

    const mailOptions = {
      from: functions.config().gmail.email,
      to: "your-contact@yourdomain.com",
      subject: `お問い合わせ from ${name}`,
      text: `名前: ${name}\nメール: ${email}\n内容:\n${message}`
    };

    try {
      await transporter.sendMail(mailOptions);
      res.status(200).send("Email sent successfully");
    } catch (error) {
      console.error("Error sending email:", error);
      res.status(500).send("Internal Server Error");
    }
  });
  ```

- Cloud Functions をデプロイした後、Nuxt 側からは通常の POST リクエストでこの Function を呼び出せます。

## 6. まとめ

- Nuxt.js と Firebase（Firestore や Cloud Functions）を利用すれば、比較的シンプルにお問い合わせフォームを実装できます。  
- スパム対策は、まずハニーポットフィールドを追加するシンプルな方法があり、さらに Google reCAPTCHA を導入すれば、ボットからの自動送信をより確実に防げます。  
- プロジェクトの要件に合わせて、Firestore へ保存する方法と Cloud Functions 経由でメール送信する方法を組み合わせることで、運用に適したフォームが実現できます。

このように、Firebase と Nuxt.js を組み合わせることで、無料のサービス内でお問い合わせフォームとスパム対策を実現することが可能です。ぜひお試しください。

---
## **💖 ご支援いただけませんか？**

![スクリーンショット 2025-01-31 7.51.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3637864/f9d94b4d-dbd3-ab70-6b2b-dfc289cda562.png)


このブログでは、高品質な情報提供と学習活動を通じて、読者の皆さまのお役に立つことを目指しています。もしこの記事が役立ったと感じていただけましたら、ご支援いただけると幸いです！

---

### **暗号資産による寄付**
以下のウォレットアドレスをご利用ください。**重要：Ethereum (ETH)、BNB Chain (BNB)、Polygon (MATIC)、Avalanche (AVAX) は、全て以下の同一アドレスを使用しますが、送金ネットワークの選択を間違えると資金が失われます！** 送金時には、**絶対に**使用するネットワーク（例: ERC-20、BEP-20、Polygon、Avalanche C-Chain）を**必ず**正しく選択してください。

---

<img src="https://cryptologos.cc/logos/ethereum-eth-logo.png" alt="Ethereum Logo" width="20">

**Ethereum (ETH)**  （ネットワーク: ERC-20）

```
0x5CDA2F68f59F641B00aD172475c3d5fC10321174
```
<img src="https://cryptologos.cc/logos/binance-coin-bnb-logo.png" alt="BNB Logo" width="20">

**BNB Chain (BNB)**  （ネットワーク: BEP-20）
```
0x5CDA2F68f59F641B00aD172475c3d5fC10321174
```
<img src="https://cryptologos.cc/logos/polygon-matic-logo.png" alt="Polygon Logo" width="20">

**Polygon (MATIC)**   （ネットワーク: Polygon）

```
0x5CDA2F68f59F641B00aD172475c3d5fC10321174  
```
<img src="https://cryptologos.cc/logos/avalanche-avax-logo.png" alt="Avalanche Logo" width="20">

**Avalanche (AVAX)**    （ネットワーク: Avalanche C-Chain）

```
0x5CDA2F68f59F641B00aD172475c3d5fC10321174
```
<img src="https://cryptologos.cc/logos/solana-sol-logo.png" alt="Solana Logo" width="20">

**Solana (SOL)**  
```
EnPFbqDbF67rU9mAPvfgh4YYtncJNbFQ9NLQ5R6z5S2f
```

<img src="https://cryptologos.cc/logos/stellar-xlm-logo.png" alt="Stellar Logo" width="20">

**Stellar (XLM)**    メモ: 必要に応じて入力してください。

```
GCSMWCACKVEZ737GZAV4AJRFL52ZZKVQ7M3B3KYY64JJGOAO2GDYKABO 
```

<img src="https://cryptologos.cc/logos/xrp-xrp-logo.png" alt="Ripple Logo" width="20"> 

**Ripple (XRP)**    タグ: 必要に応じて入力してください。

```
r1s4EASr3zQRrfpDA3ptTahezBhGo2hhN
```

<img src="https://cryptologos.cc/logos/cardano-ada-logo.png" alt="Cardano Logo" width="20">

**Cardano (ADA)**  

```
addr1q8heq6ddw8rwlqa5hqlucnfk36arah9tzc8ajxvu83870h7lrre25wzq9yemex857we56cm0xu8tmxqvm8nykmtgsjdqavdpv7
```

<img src="https://cryptologos.cc/logos/dogecoin-doge-logo.png" alt="Dogecoin Logo" width="20">

**Dogecoin (DOGE)**  
```
DRFZ9JhAk3DTtu1tV85cawekWNrm1vKm3H
```

---

### **資金用途**
寄付金は以下の目的で活用させていただきます：

1. サーバー維持費やデザインツール購入  
2. 学習活動（オンラインコース受講・書籍購入）  
3. 読者向け無料コンテンツ制作  

ご協力いただいた皆さまには心より感謝申し上げます！ 🙏

---

### **補足情報**
- **Ethereum (ETH)、BNB Chain (BNB)、Polygon (MATIC)、Avalanche (AVAX)について**  
  上記4つのネットワークは**同じウォレットアドレス**（`0x5CDA2F68f59F641B00aD172475c3d5fC10321174`）を使用します。ただし、送金時には、**絶対に**使用するネットワーク（例: ERC-20、BEP-20、Polygon、Avalanche C-Chain）を**必ず**正しく選択してください。

- USDCやUSDTなどのステーブルコインも、対応するネットワーク経由であれば送金可能です。ただし、**送金先のネットワークと選択するネットワークが一致していることを必ず確認してください。**

- 初回送金時には少額でテスト送金することをおすすめします。

---