NewtとNuxt3を利用して、問い合わせフォームを作成する

最終更新日:

Table of contents

このチュートリアルでは、Newtの Form AppNuxt3 を利用して、問い合わせフォームを作成する手順を紹介します。

記事内で使用している主なソフトウェアのバージョン

  • Nuxt(nuxt): 3.10.3
  • VeeValidate(vee-validate): 4.12.6
  • Vue reCAPTCHA-v3(vue-recaptcha-v3): 2.0.1

前提条件

  • Nuxt3のプロジェクトを作成済みであること

Nuxt3のセットアップについて知りたい場合は、以下のドキュメントをご確認ください。

概要

Nuxt3で問い合わせフォームを作成し、Newtで問い合わせデータを確認できるようにします。
はじめに、自動返信・受信通知のついたシンプルなフォームを作成し、その後でバリデーション・スパム対策・リダイレクトの処理を実装しながら、カスタマイズの方法を学習していきます。
※ ここではスタイルについては扱いません。スタイルについて確認したい方は、Next.jsのコードとなりますが、問い合わせ用のテンプレート newt-starter-nextjs-contact を用意しているので、そちらのコードをご確認ください。

1. Form Appを作成する

はじめに、NewtでForm Appを作成します。

1-1. Form Appの追加

「Appを追加」をクリックして「タイプを選択して追加」を選択します。
form-tutorial1.jpg

「Form App」を選択して「追加」をクリックします。
form-tutorial2.jpg

App名・App UID・Appアイコンを設定して「追加」をクリックします。
form-tutorial3.jpg

これで「Contact」という名前のForm Appが追加されました。
form-tutorial4.jpg

1-2. フォームの作成

次にフォームを作成します。「フォームを作成」ボタンをクリックし、名前を付けてフォームを作成します。
form-tutorial5.jpg

作成後に表示されるエンドポイントは、2-1で利用します。
form-tutorial6.jpg

2. リクエストの準備

2-1. 環境変数の設定

Nuxtの Runtime Config を利用して、環境変数を利用できるようにします。

ここでは、.env ファイルを作成し、1-2で確認したエンドポイントの値を定義します。以下を、実際の値で置き換えて定義してください。

.env
1NUXT_PUBLIC_NEWT_FORM_ENDPOINT=https://xxxxxx.form.newt.so/v1/xxxxxx

また、nuxt.config.tsruntimeConfigpublic.newt.formEndpoint を追加します。

nuxt.config.ts
  runtimeConfig: {
    public: {
      newt: {
        formEndpoint: ''
      }
    }
  },

上記のように定義しておくと、runtimeConfig.public.newt.formEndpoint の値は .env ファイルの NUXT_PUBLIC_NEWT_FORM_ENDPOINT の値に置き換えられます。

3. Nuxt3でシンプルな問い合わせフォームを作成する

続いて、Nuxt3でシンプルな問い合わせフォームを作成しましょう。
ここでは /contact というパスにアクセスした時に、問い合わせフォームを表示するようにします。

3-1. HTMLフォームの追加

pages/contact.vue を作成し、以下のように記述します。

pages/contact.vue
1<script lang="ts" setup>
2const config = useRuntimeConfig()
3
4useHead({
5  title: 'Newt・Nuxtフォーム',
6  meta: [
7    { name: 'description', content: 'NewtとNuxtを利用した問い合わせフォームです' }
8  ]
9})
10</script>
11
12<template>
13  <div>
14    <h1>Contact us</h1>
15    <form :action="config.public.newt.formEndpoint" method="post">
16      <label for="name">Name</label>
17      <input id="name" name="name">
18      <label for="email">Email</label>
19      <input id="email" name="email" type="email">
20      <label for="message">Message</label>
21      <textarea id="message" name="message" />
22      <button type="submit">
23        Submit
24      </button>
25    </form>
26  </div>
27</template>

各属性について説明します。

label要素のfor属性と、input要素・textarea要素のid属性
label要素for 属性は、input要素textarea要素id 属性と一致するように設定します。

input要素・textarea要素のname属性
input要素・textarea要素のnameで設定された値をもとに、投稿データのスキーマが決まります。例えば上記の例では、nameemailmessage フィールドを持ったスキーマが作成されます。
詳細については、フィールドの仕様 のドキュメントをご確認ください。

input要素のtype属性
お好みの入力形式に応じて、inputの型 を選択してください。デフォルトでは text となります。

これで http://localhost:3000/contact にアクセスすると、以下のような問い合わせフォームが作成できました。
※ スタイルについてはお好みで設定してください
nuxt3-form1.png

3-2. 自動返信メールの設定

自動返信メール の設定をします。自動返信メールを設定することで、投稿を送信したエンドユーザーに対して、設定した内容のメールを自動で送信できます。

※ Newtは自動返信メールの送信先メールアドレスを検出するために email フィールドを使います。input要素のname属性に email を指定し、エンドユーザーのメールアドレスが入力されるようにしてください。

「App設定」から該当のフォームをクリックし、「メール設定」を選択します。
「自動返信メールを有効にする」にチェックを入れ、件名と本文を入力し、「保存」をクリックします。

form-tutorial7.jpg

自動返信メールでは {submission.name} のように記述することで、メールの件名や本文に、投稿データの内容を埋め込めます。
3-1で、input要素・textarea要素のname属性に nameemailmessage を指定したので、ここでは {submission.name}{submission.email}{submission.message} を利用できます。

3-3. 受信通知メールの設定

受信通知メール の設定をします。受信通知メールを設定することで、エンドユーザーから新しいメッセージを受信した際に、メールで通知を受け取れます。

「App設定」から該当のフォームをクリックし、「メール設定」を選択します。
「受信通知メールを有効にする」にチェックを入れ、通知を受け取るメールアドレス・件名・本文を設定し、「保存」をクリックします。

form-tutorial8.jpg

受信通知メールでも、自動返信メールと同様に、投稿データの内容を埋め込めます。

3-4. 挙動の確認

これで、シンプルな問い合わせフォームを作成できました。
実際に動かして、挙動を確認してみましょう。

まず、開発サーバーを立ち上げて、問い合わせフォームを表示します。ここでは http://localhost:3000/contact にアクセスします。
適当な内容を入力して送信すればよいですが、設定したEmail(name属性に email を指定した入力値)に自動返信メールが届くので、ご自身のメールアドレスを入力するよう気をつけてください。
nuxt3-form2.png

送信が成功すると、以下の画面が表示されます。
form-tutorial9.png

管理画面では、以下のようにデータが確認できます。
form-tutorial10.jpg

次に自動返信メールを確認します。以下のようなメールが飛んでいれば成功です。
form-tutorial11.png

最後に受信通知メールを確認します。以下のようなメールが飛んでいれば成功です。
form-tutorial12.png

これでシンプルなフォームの作成・自動返信メールの設定・受信通知メールの設定ができました。ここからはフォームのカスタマイズをしながら、より高度なフォームの作成方法を学んでいきましょう。

4. バリデーションを追加する

クライアントサイドのバリデーションについて、以下の順で紹介します。

  • input要素の属性を利用したバリデーション
  • JavaScriptを利用したバリデーション(VeeValidateを利用したバリデーション)

4-1. input要素の属性を利用したバリデーション

HTML5のフォームバリデーションを利用することで、JavaScriptなしでバリデーションを実行できます。よく使われるものについては HTML5のフォームバリデーション をご確認ください。

例えば、name を必須にしたい場合は、以下のように required を指定します。

<input id="name" name="name" required />

もし name を指定せず、送信を実行した場合は、以下のように表示されます。
nuxt3-form3.png

4-2. JavaScriptを利用したバリデーション

HTMLネイティブのフォームバリデーションでは足りない場合、JavaScriptを利用することで、より柔軟なバリデーションを実行できます。
ここでは、バリデーションのタイミングを変え、さらにエラーメッセージをカスタマイズしてみましょう。

JavaScriptを利用してフォームを送信する場合(Acceptヘッダーでapplication/jsonを指定してレスポンスをJSONデータとして受け取る場合)、ご自身で送信後の表示を制御していただくことになります。
デフォルトのThanksページ・Errorページへのリダイレクトや カスタムリダイレクト は行われなくなりますので、ご注意ください。

ここでは VeeValidate を利用してバリデーションを行います。まずは vee-validate をインストールしましょう。

npm install vee-validate
# or
yarn add vee-validate

続いて、以下のように pages/contact.vue を修正します。

pages/contact.vue
1<script lang="ts" setup>
2import { useForm } from 'vee-validate'
3
4const config = useRuntimeConfig()
5
6const schema = {
7  name (value: string) {
8    return value ? true : 'Name is required'
9  }
10}
11const { defineField, handleSubmit, errors } = useForm({
12  validationSchema: schema
13})
14const [name, nameProps] = defineField('name')
15const [email, emailProps] = defineField('email')
16const [message, messageProps] = defineField('message')
17
18const onSubmit = handleSubmit(async (values) => {
19  const formData = new FormData()
20  Object.entries(values).forEach(([key, value]) => {
21    formData.append(key, value)
22  })
23
24  await fetch(config.public.newt.formEndpoint, {
25    method: 'POST',
26    body: formData,
27    headers: {
28      Accept: 'application/json'
29    }
30  })
31})
32
33useHead({
34  title: 'Newt・Nuxtフォーム',
35  meta: [
36    { name: 'description', content: 'NewtとNuxtを利用した問い合わせフォームです' }
37  ]
38})
39</script>
40
41<template>
42  <div>
43    <h1>Contact us</h1>
44    <form @submit="onSubmit">
45      <label for="name">Name*</label>
46      <input id="name" v-model="name" v-bind="nameProps" name="name" aria-describedby="error-name-required">
47      <span v-if="errors.name" id="error-name-required" aria-live="assertive">
48        {{ errors.name }}
49      </span>
50      <label for="email">Email</label>
51      <input id="email" v-model="email" v-bind="emailProps" name="email" type="email">
52      <label for="message">Message</label>
53      <textarea id="message" v-model="message" v-bind="messageProps" name="message" />
54      <button type="submit">
55        Submit
56      </button>
57    </form>
58  </div>
59</template>

以下のことを行っています。

  • validationSchema に渡す schema で、name がない場合に Name is required というエラーメッセージを設定しています
  • 入力されたフォームのデータは handleSubmit 関数で values として利用できます
  • FormData を利用して、送信するデータ formData を作成します
  • フェッチ API を利用して、エンドポイントにデータを送ります。この時、Acceptヘッダーに 'application/json' を指定します

また、以下の部分で validationSchema で設定したエラーメッセージを出しています。(ここでは name がない場合に Name is required というエラーメッセージを出しています)
validationSchema を利用して、様々なバリデーションが設定できるので、ぜひお好きなバリデーションをお試しください。

1<label for="name">Name*</label>
2<input id="name" v-model="name" v-bind="nameProps" name="name" aria-describedby="error-name-required">
3<span v-if="errors.name" id="error-name-required" aria-live="assertive">
4  {{ errors.name }}
5</span>

以下のことを確認します。

  • 入力変更があったタイミングでバリデーションが実行されること
  • name が入力されていない場合に Name is required というエラーメッセージが出ること

nuxt3-form4.png

5. スパム対策を実装する

続いて、スパム対策 として Google reCAPTCHA v3 の設置方法について説明します。

5-1. サイトキーとシークレットキーの取得

Google reCAPTHAのコンソール にアクセスし、新しいサイトを登録します。

reCAPTCHAタイプは reCAPTCHA v3 を選択します。
ここではローカル環境でのテストを行うので、ドメインには localhost を追加します。本番環境で設定したい場合は、reCAPTCHAを導入したいサイトのドメインを設定してください。
form-tutorial13.jpg

全ての項目を入力して「送信」をクリックすると、サイトキーとシークレットキーが表示されます。
form-tutorial14.jpg

5-2. シークレットキーの登録

Newtの管理画面にアクセスし、「App設定」を開きます。該当のフォームをクリックし、「フォーム設定」を選択します。
「スパム対策」のセクションより、「Google reCAPTCHA v3を有効にする」にチェックを入れ、5-1で取得したシークレットキーを貼り付けて保存します。
spam-filtering1.png

5-3. Nuxtでの設定

最後にNuxtでの設定を行います。ここでは Vue reCAPTCHA-v3 を用いて、設定します。まずはvue-recaptcha-v3をインストールしましょう。

npm install vue-recaptcha-v3
# or
yarn add vue-recaptcha-v3

続いて、環境変数に NUXT_PUBLIC_NEWT_RECAPTCHA_SITE_KEY を登録します。

.env
NUXT_PUBLIC_NEWT_FORM_ENDPOINT=https://xxxxxx.form.newt.so/v1/xxxxxx
NUXT_PUBLIC_NEWT_RECAPTCHA_SITE_KEY=xxxx

あわせて、nuxt.config.tsruntimeConfigpublic.newt.recaptchaSiteKey を追加します。

nuxt.config.ts
  runtimeConfig: {
    public: {
      newt: {
        formEndpoint: '',
        recaptchaSiteKey: ''
      }
    }
  },

pages/contact.vue を、以下のように修正します。

pages/contact.vue
1<script lang="ts" setup>
2import { useForm } from 'vee-validate'
3import { VueReCaptcha, useReCaptcha } from 'vue-recaptcha-v3'
4
5const config = useRuntimeConfig()
6
7const schema = {
8  name (value: string) {
9    return value ? true : 'Name is required'
10  }
11}
12const { defineField, handleSubmit, errors } = useForm({
13  validationSchema: schema
14})
15const [name, nameProps] = defineField('name')
16const [email, emailProps] = defineField('email')
17const [message, messageProps] = defineField('message')
18
19const { vueApp } = useNuxtApp()
20vueApp.use(VueReCaptcha, {
21  siteKey: config.public.newt.recaptchaSiteKey,
22  loaderOptions: {
23    renderParameters: {
24      hl: 'ja'
25    }
26  }
27})
28const recaptchaInstance = useReCaptcha()
29
30const onSubmit = handleSubmit(async (values) => {
31  await recaptchaInstance?.recaptchaLoaded()
32  const token = await recaptchaInstance?.executeRecaptcha('submit')
33  values.googleReCaptchaToken = token
34
35  const formData = new FormData()
36  Object.entries(values).forEach(([key, value]) => {
37    formData.append(key, value)
38  })
39
40  await fetch(config.public.newt.formEndpoint, {
41    method: 'POST',
42    body: formData,
43    headers: {
44      Accept: 'application/json'
45    }
46  })
47})
48
49useHead({
50  title: 'Newt・Nuxtフォーム',
51  meta: [
52    { name: 'description', content: 'NewtとNuxtを利用した問い合わせフォームです' }
53  ]
54})
55</script>
56
57<template>
58  <div>
59    <h1>Contact us</h1>
60    <form @submit="onSubmit">
61      <label for="name">Name*</label>
62      <input id="name" v-model="name" v-bind="nameProps" name="name" aria-describedby="error-name-required">
63      <span v-if="errors.name" id="error-name-required" aria-live="assertive">
64        {{ errors.name }}
65      </span>
66      <label for="email">Email</label>
67      <input id="email" v-model="email" v-bind="emailProps" name="email" type="email">
68      <label for="message">Message</label>
69      <textarea id="message" v-model="message" v-bind="messageProps" name="message" />
70      <button type="submit">
71        Submit
72      </button>
73    </form>
74  </div>
75</template>

以下のことを行っています。

  • VueReCaptchauseReCaptcha をインポートします
  • VueReCaptchaオプション として、siteKeyloaderOptions を設定しています。ここでは hlja を設定しているため、問い合わせフォームの右下に表示されるreCAPTCHAのバッジが日本語になります。他の言語で設定したい方は、Googleの Language Codes のドキュメントを参考に設定してください
  • googleReCaptchaToken というフィールド名でトークンを送信します

これでGoogle reCAPTCHA v3によるスパム対策が実装できました。

form-tutorial15.jpg

もし、バッジを非表示にしたい場合、loaderOptions.autoHideBadgetrue にすると非表示になりますが、この操作はGoogle公式のガイドに従った場合のみ許可されます。
詳細は、Googleの reCAPTCHA バッジを非表示にします。どうすればよいですか? のドキュメントをご確認ください。

6. リダイレクトをカスタマイズする

最後にフォーム送信後のリダイレクトをカスタマイズします。
送信が成功した場合のページと、失敗した場合のページを用意して、送信結果に応じてリダイレクトしてみましょう。

6-1. 送信成功ページ・送信失敗ページを用意する

送信成功ページとして、以下を用意します。
pages/thanks.vue を作成します。

pages/thanks.vue
1<script lang="ts" setup>
2useHead({
3  title: 'Thank you',
4  meta: [
5    { name: 'description', content: '問い合わせの送信が成功しました' }
6  ]
7})
8</script>
9
10<template>
11  <div>
12    <h1>Thank you!</h1>
13    <div>
14      <NuxtLink href="/contact">
15        Back to Previous Page
16      </NuxtLink>
17    </div>
18  </div>
19</template>

送信失敗ページとして、以下を用意します。
pages/error.vue を作成します。

pages/error.vue
1<script lang="ts" setup>
2useHead({
3  title: 'Error',
4  meta: [
5    { name: 'description', content: '問い合わせの送信が失敗しました' }
6  ]
7})
8</script>
9
10<template>
11  <div>
12    <h1>Error!</h1>
13    <div>
14      <NuxtLink href="/contact">
15        Back to Previous Page
16      </NuxtLink>
17    </div>
18  </div>
19</template>

6-2. リダイレクトを設定する

pages/contact.vue を以下のように修正します。

pages/contact.vue
<script lang="ts" setup>
(省略)

const onSubmit = handleSubmit(async (values) => {
  await recaptchaInstance?.recaptchaLoaded()
  const token = await recaptchaInstance?.executeRecaptcha('submit')
  values.googleReCaptchaToken = token

  const formData = new FormData()
  Object.entries(values).forEach(([key, value]) => {
    formData.append(key, value)
  })

  await fetch(config.public.newt.formEndpoint, {
    method: 'POST',
    body: formData,
    headers: {
      Accept: 'application/json'
    }
  })
  try {
    const response = await fetch(config.public.newt.formEndpoint, {
      method: 'POST',
      body: formData,
      headers: {
        Accept: 'application/json'
      }
    })

    if (response.ok) {
      await navigateTo('/thanks')
    } else {
      await navigateTo('/error')
    }
  } catch (err) {
    await navigateTo('/error')
  }
})

(省略)
</script>

<template>
  (省略)
</template>

送信が成功した場合(response.ok がtrueの場合)は navigateTo を利用して /thanks にリダイレクトし、送信成功ページを表示しています。
送信が失敗した場合(response.ok がfalseの場合、またはエラーが発生した場合)は /error にリダイレクトし、送信失敗ページを表示しています。

これでリダイレクトをカスタマイズすることができました。

以上で、すべてのステップが終了となります。

Form Appでは、他にもさまざまな機能があるので、興味のある方はぜひ以下のドキュメントもご覧ください。

NewtMade in Newt