【React初心者向け】Vercel v0で問い合わせフォームを作る ～デプロイ編～

# UI生成AI『v0』

[v0](https://v0.dev/)はVercel Labs社が開発したウェブデザインに特化した生成AIツールです。
WebサイトやWebアプリはもちろんプレゼン用のスライドや図解資料など様々なものをチャット形式でアシスタントが生成してくれます。

前回生成したものをローカルで開発できるようにしましたが、今回は実際に誰でも動かせるように[デプロイ](https://swri.jp/glossary/%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4#:~:text=%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%EF%BC%88deploy%EF%BC%89%E3%81%AF%E3%80%81%E3%80%8C,%E4%B8%80%E9%80%A3%E3%81%AE%E4%BD%9C%E6%A5%AD%E3%82%92%E6%8C%87%E3%81%99%E3%80%82)までしてみたいと思います。

https://qiita.com/nekoniii3/items/e8aaf7762110a8be9b84

※概ねAIに開発してもらいますが、一部は手作業で修正を行います

<br>

今回も前回の続きとして『問い合わせフォーム』を作りたいと思います。






ただ問い合わせフォーム言っても、単に『入力された内容をGmailで送る』というだけでデータベースに内容を保管するとか状態管理するとかはしません。


もちろん今の時代問い合わせフォームは[Google Forms](https://www.google.com/intl/ja_jp/forms/about/)など様々なサービスがあるので自分で作る必要などないのですが、v0で作った方が見た目が綺麗ですし

### こいつできる…

<br>
と思われること間違いなしです。（費用も無料です）

:::note
利用するものは
フレームワーク：Next.js
公開（デプロイ）先：Vercel
メールサービス：Gmail（nodemailerを利用）
その他：GitHub Desktop

必要なものは
・GitHubアカウント　・Googleアカウント（要アプリパスワード ※後述）
:::
※実践するPCのOSはWindows11です。

# v0を利用する

前回同様まずv0にコードを生成してもらいますが、指示は以下のようにしました。

>「お名前」「メールアドレス」「お問い合わせ内容」だけの問い合わせフォームの作成をお願いします。送信ボタンを押した後はNodemailerで内容をメールで送信し、画面を切り替えて"ありがとうございました"とメッセージを表示するようにして下さい。Server Actionsで実装をお願いします。

![スクリーンショット 2024-10-19 163335 - コピー - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/0e41cc0e-e461-61da-700b-8bc88cc01c97.png)

<br>

今回は **"Server Actionsで"** と指定しました。Server Actionsと指定しない場合はサーバ側はAPIを作り**fetch(～)** で呼び出すことになることが多いようです。

Server ActionsはNext.js v14から正式リリースされたほやほやの新機能です。なのでまだリファレンスが多くないですし、基本から理解したいという方はfetchで呼び出した方がいいかもしれません。今回はコードが少なそうという単純な理由でこちらを選びました。その他の指示はお好みで指定して下さい。<br>
![スクリーンショット 2024-10-19 163554.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/446dc39c-56e1-eedb-459a-5d56e4b90cda.png)<br>
クライアント側（**contact-form.tsx**）とサーバ側（**actions.ts**）2つのファイルを生成してくれました。クライアント側の方は選択して右上の[Add to Codebase]をクリックしてインストール用コマンドをコピーして下さい。サーバ側はファイルをダウンロードしておいて下さい。（サーバ側もコマンドが使えますが、ただファイルをもってくるだけのようなのでダウンロードしておきました。）<br>
![Snapshot_142.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/b75971e2-6e06-9a28-9402-ce20c647eb76.png)

![Snapshot_144.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/bea49360-e1f0-adc0-fb80-25b0bc444ca8.png)



## ローカル環境での作業

デプロイ前にこれらを自分のPCで動かせるようにします。前回と一緒のところは説明を省略します。

※PCにはNode.jsをインストールしておいて下さい。（[詳細はこちら](https://qiita.com/gahoh/items/8444da99a1f93b6493b4)）

```Powershell:Next.js プロジェクト作成
npx create-next-app@latest
```
```Powershell:カレントディレクトリ移動
cd [作成したフォルダ名]
```

先ほどコピーしたコマンドをここで使います。
```Powershell:生成したコンポーネントの追加 ※コピーしたコマンドを利用して下さい
npx shadcn@latest add "https://v0.dev/chat/b/b_IYsZY80VYZP?token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..LNFwJsmWN_JWnOO1.-hnAiNrzYDblxYowsVUVBPRB_Fi50_v5uAIL94gbH46xJPPsYmy5k1fE_JQ.mC7s8AwYRuQ1GlLI6vw8eA"
```

このタイミングでついでにnodemailerもインストールしておきます。

```Powershell:nodemailerインストール
npm i nodemailer
npm i -D @types/nodemailer 
```
<br>

サーバ側となるactions.tsは手動で追加します（ファイルを置くだけです）。プロジェクトフォルダ以下ならどこでもいいですが今回は[プロジェクトフォルダ]下の"src"フォルダに"actions"というフォルダを作成して、そこに置きたいと思います。

```:src以下構成
src
├─actions ※手動で作成
│  └─actions.ts ※手動で追加
├─app
├─components
│  └─ui
│  └─ContactForm.tsx ※自動で追加されてます
├─hooks
└─lib
```

<br>

ここまできましたら一度立ち上げられるまで修正します。

```typescript:src/app/page.tsxの修正 ※前回と同様パスなどは変更して下さい
import { ContactFormComponent } from "@/components/contact-form"

export default function Home() {
  return (
    <ContactFormComponent/>
  );
}
```

ここからは生成結果により異なりますが、ContactForm.tsxからactions.tsアクセスするパスが違っているので修正します。

```diff_typescript:ContactForm.tsxの修正①

- import { submitForm } from './actions'

+ import { submitForm } from '../actions/actions'

```

※前回同様、必要であればuse-toastのパスを直してあげます。

```diff_typescript:ContactForm.tsxの修正② ※前回と同様です

- import { toast } from "@/components/ui/use-toast"

+ import { toast } from "@/hooks/use-toast"

```

<br>

これで一度起動してみます。

```Powershell:開発モードで起動
npm run dev
```

http://localhost:3000 にアクセスしてエラーなく立ち上がればOKです。

# nodemailerでのメール設定

起動までできましたら、メールが送れるようにしたいと思いますのでGoogleのアプリパスワードを取得します。アカウントの2段階認証が必須となっていますので[セキュリティ設定](https://myaccount.google.com/security)から2段階認証に変更の上、以下で作成して下さい。

https://myaccount.google.com/apppasswords

<br>

取得しましたらプロジェクトフォルダ直下に.env.localファイルを作成してそこに環境変数として設定します。（テキストファイルとして作成し名前を".env.local"にして下さい。）

```:プロジェクトフォルダ構成
[プロジェクト名]
├─.next
├─node_modules
├─src
├─.env.local ※新規追加
└─.gitignore
… (以下省略)
```
※[ほんとはダメらしい](https://qiita.com/ken-s/items/b6190283db28295155bb)ですが、今回は簡略化のためここに設定します


.env.localの設定例はこちらです。※変数名は1例で生成したソースよって異なります
```python:.env.local
SMTP_USER = "**********@gmail.com" # 送信元のアドレス
SMTP_PASS = "************" # 取得したアプリパスワード（スペースは消してください）
MAIL_FROM = '"お問い合わせフォーム" <noreply@example.com>'
MAIL_TO = "**********@****.****" # 送信先のアドレス
```

<br>

SMTP_USERが**送信元**のアドレス（アプリパスワードを取得したアカウント）となり、MAIL_TOが通知先の任意のメールアドレスとなります。
※今回は自身への通知が目的で問い合わせした人にメールを返信する仕様ではありません

MAIL_FROMは送信元になるのですが空白でもSMTP_USERになるので空白でも構いません。
ただこうやって書くとメールBOXで"お問い合わせフォーム"と表示されるので便利かと思います。（noreply@example.comはそのままです。仕組みはわかりません…）

あとはGmailが送れるようにソースの一部を修正します。

```ts:actions.ts修正
export async function submitForm(formData: FormData) {
  const name = formData.get('name') as string
  const email = formData.get('email') as string
  const message = formData.get('message') as string
  
  const transporter = nodemailer.createTransport({
    host: "smtp.gmail.com",
    port: 587,
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
    },
  })

  try {
    // メールを送信
    await transporter.sendMail({
      from: process.env.MAIL_FROM,
      to: process.env.MAIL_TO,
      subject: '新しい問い合わせ',
      text: `
        名前: ${name}
        メールアドレス: ${email}
        メッセージ: ${message}
      `,
    })
    …(省略)
```

※長いので一部のみを表示しています。全ソースは[こちらから](https://github.com/nekoniii3/contact_form/tree/master/src)ご確認ください。

hostを<b>"smtp.gmail.com"</b>にして、portを<b>587</b>にしました。
あとはもう[nodemailer](https://nodemailer.com/)の使い方に従い.env.localの内容を当てはめるだけです。

これでフォームの内容を適当に入れて送信できるかテストしてみて下さい。
意外とあっさりメールが送れるはずです。

## Vercelへデプロイ
ではこれをVercelにデプロイ、公開したいと思います。
Vercel以外にも[Netlif](https://www.netlify.com/)などのホスティングサービスがありますが、Vercelで作ったのでVercelに上げるのが筋なような気がしたので、そのままVercelにアップします。

まずは先にGitHubにプッシュします。（VercelがGitHubと連携できる仕組みがあります）
私はGitHub Desktopを利用しますが、日常的にGitを使っている強者の方はコマンドでやっちゃって下さい。（インストールは以下を参考にして下さい。）

https://www.kagoya.jp/howto/it-glossary/develop/githubdesktop/

<br>

GitHub Desktopインストール後にGitHubアカウントでログイン。
そして先ほど作ったNext.jsのプロジェクトを[Add local repository]からリポジトリに追加します。
![Snapshot_145.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/3c35b0b5-f414-cad8-2ebb-f13a03a4c436.png)

先ほどのプロジェクトのパスを指定して[Add repository]。
![Snapshot_146.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/5b5e66e5-c1c9-53ed-5b0d-9692397ea57f.png)

①適当にコメントを入れて②コミット③プッシュします。
![Snapshot_147.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/159b53be-356b-3fa7-d520-8de171fde63a.png)

名前を設定。リポジトリはプライベート・パブリックどちらでも構いません。
![Snapshot_148.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/fb6382d5-4941-60d4-41a3-46344da5b223.png)

![Snapshot_149.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/620b0156-5041-d7fa-a254-f7231a50092c.png)


リポジトリが作成されました。ちなみに.env.localはリポジトリに含まれません。.gitignoreに記載されているとプッシュされない仕組みとなっています。

<br>

あとはこれをVercelと連携するだけです。Vercelにログイン。新しいプロジェクトを追加します。
![Snapshot_150.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/a714c7cb-ac54-f2cc-3b6f-b1690c2133ad.png)

GitHubと連携してさ作成したリポジトリを追加します。
![Snapshot_151.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/658151a6-607a-d94b-620c-ece154763a35.png)

![Snapshot_152.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/bd75f757-963c-93e5-815a-3df652fb23c5.png)

名前と環境変数を設定してデプロイ。ここで.env.localに記載していた情報を設定します。
![Snapshot_153.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/50730b9c-81bc-ab77-bca7-78849e7df229.png)

![Snapshot_154.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/3e44d190-11c1-dfd0-321d-0152e44d40e4.png)

![Snapshot_155.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/6928a7dc-28da-cbc7-c7bb-dfa5f9139adf.png)


デプロイに少々時間がかかります。何もエラーにならなければ成功です。

![Snapshot_156.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/74213705-247d-0d53-902b-b60eeb28b543.png)

<br>

プロジェクトページの[Domaine]が公開されるアドレスとなるので誰でもアプリが起動できるようになります。これで問い合わせフォーム完成です！（[完成品](https://contact-form-five-mu.vercel.app/)）

![Snapshot_157.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/80cf225f-d7b4-d3d9-5e02-644208207928.png)

![Snapshot_158.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/87ce5472-a95f-c03d-3c45-9cea1d2e949d.png)


修正する場合は[ローカルでコードを変更]→[GitHubにプッシュ]という流れとなります。

**※環境変数がうまく設定できてないことがありましたので、その場合プロジェクトページのここから設定して下さい**
![Snapshot_159.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3549938/6f780713-2ada-e688-a977-47d13b437705.png)


# おわりに

生成したコードで変わってきますので、同じようにして上手く行くか怪しいですがそこまで複雑なコードでもないですので、エラーになりましたら[こちらのソース](https://github.com/nekoniii3/contact_form/tree/master/src)と見比べてみてください。


今回はシンプルすぎる見た目ですが、色々注文すればもっとおしゃれな感じになるかと思います！

# 補足とたわごと

React入門者の方に補足です。（※偉そうですが私も勉強の身です）

「**クライアント側（contact-form.tsx）とサーバ側（**actions.ts**）としてソースが生成されました**」と言いましたが、適当なフォルダに配置したファイルなのに何が違うのか？と疑問に持った方もいるかと思います。

この違いは拡張子…ではなく、それぞれの先頭の1行

```tsx:
'use client'
```
```tsx:
'use server'
```

によってそれぞれ動作する場所を指示されています。

（ですのでconsole.log(～)で出力した場合もクライアント側はchormeの**検証**などで確認できますが、サーバー側は検証には出力されず、実行中のコンソールからしか確認できません。）

**これだけ？**


と思うかもしれません。実際に誤りが起きやすいためサーバーで実行されるはずのコードがクライアントで実行される[セキュリティ上の懸念](https://azukiazusa.dev/blog/why-use-server-actions/#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E4%B8%8A%E3%81%AE%E6%87%B8%E5%BF%B5)もあるとされています。

そういう意味ではServer Actionsを使わずサーバー側はGETやPOSTで待ち構える**サーバーサーバー**してるコードの方が誤りは起きにくいのかもしれません。

しかし、Next.jsなどReactフレームワークに入門すると<b>「フルスタック！」「フルスタック！」</b>と煽られるわりにやることはAPIで呼び出すなんとも**家庭内別居**のような関係でがっかりした記憶もあります。
（※フルスタックはフロントエンドとバックエンドの両方を開発できること）

フルスタックなんだからクライアントとサーバーは同居すべきか。それともセキュリティのため別居すべきか。

という議論がありそうです。

いや無いかもしれませんw。以上たわごとでした。