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

最終更新日:

Table of contents

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

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

  • SvelteKit(@sveltejs/kit): 2.5.1
  • Felte(felte): 1.2.14

前提条件

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

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

概要

SvelteKitで問い合わせフォームを作成し、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. 環境変数の設定

SvelteKitの環境変数は、プロジェクトディレクトリの .env ファイルから読み込めます。

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

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

上記のように定義しておくと、以下のような形で利用できます。

import { PUBLIC_NEWT_FORM_ENDPOINT } from '$env/static/public'

ここで使われている $env/static/public ですが、この static は、これらの環境変数がビルド時に解決され、静的に置き換えられることを示しています。
クライアントサイドに公開したくない環境変数の場合、$env/static/private も利用できます。

またビルド時ではなく、実行時に環境変数の値を読みこむ必要がある場合、$env/dynamic/private$env/dynamic/public も利用できます。

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

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

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

src/routes/contact/+page.svelte を作成し、以下のように記述します。

src/routes/contact/+page.svelte
1<script lang="ts">
2  import { PUBLIC_NEWT_FORM_ENDPOINT } from '$env/static/public'
3</script>
4
5<svelte:head>
6  <title>Newt・SvelteKitフォーム</title>
7  <meta name="description" content="NewtとSvelteKitを利用した問い合わせフォームです" />
8</svelte:head>
9
10<div>
11  <h1>Contact us</h1>
12  <form action={PUBLIC_NEWT_FORM_ENDPOINT} method="post">
13    <label for="name">Name</label>
14    <input id="name" name="name" />
15    <label for="email">Email</label>
16    <input id="email" name="email" type="email" />
17    <label for="message">Message</label>
18    <textarea id="message" name="message" />
19    <button type="submit">Submit</button>
20  </form>
21</div>

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

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

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

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

これで http://localhost:5173/contact にアクセスすると、以下のような問い合わせフォームが作成できました。
※ スタイルについてはお好みで設定してください
sveltekit-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:5173/contact にアクセスします。
適当な内容を入力して送信すればよいですが、設定したEmail(name属性に email を指定した入力値)に自動返信メールが届くので、ご自身のメールアドレスを入力するよう気をつけてください。
sveltekit-form2.png

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

npm install felte
# or
yarn add felte

続いて、以下のように src/routes/contact/+page.svelte を修正します。

src/routes/contact/+page.svelte
1<script lang="ts">
2  import { PUBLIC_NEWT_FORM_ENDPOINT } from '$env/static/public'
3  import { createForm } from 'felte'
4
5  type FormValues = {
6    name: string
7    email: string
8    message: string
9  }
10
11  const { form, errors } = createForm<FormValues>({
12    validate: (values) => {
13      const errors: Record<string, string> = {}
14      if (!values.name) {
15        errors.name = 'Name is required'
16      }
17      return errors
18    },
19    onSubmit: async (values) => {
20      const formData = new FormData()
21      Object.entries(values).forEach(([key, value]) => {
22        formData.append(key, value)
23      })
24
25      await fetch(PUBLIC_NEWT_FORM_ENDPOINT, {
26        method: 'POST',
27        body: formData,
28        headers: {
29          Accept: 'application/json'
30        }
31      })
32    }
33  })
34</script>
35
36<svelte:head>
37  <title>Newt・SvelteKitフォーム</title>
38  <meta name="description" content="NewtとSvelteKitを利用した問い合わせフォームです" />
39</svelte:head>
40
41<div>
42  <h1>Contact us</h1>
43  <form use:form>
44    <label for="name">Name*</label>
45    <input id="name" name="name" aria-describedby="error-name-required" />
46    {#if $errors.name}
47      <span id="error-name-required" aria-live="assertive">
48        {$errors.name}
49      </span>
50    {/if}
51    <label for="email">Email</label>
52    <input id="email" name="email" type="email" />
53    <label for="message">Message</label>
54    <textarea id="message" name="message" />
55    <button type="submit">Submit</button>
56  </form>
57</div>

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

  • 入力値の型として、FormValues を定義し、createForm にジェネリクスとして渡します
  • createFormvalidate でバリデーション関数を定義できます。ここでは、name がない場合に Name is required というエラーメッセージを設定しています
    ※ 詳細はFelteの Validation のドキュメントをご確認ください
  • 入力されたフォームのデータは onSubmit 関数で values として利用できます
    ※ 詳細はFelteの Submitting のドキュメントをご確認ください
  • FormData を利用して、送信するデータ formData を作成します
  • フェッチ API を利用して、Form Appのエンドポイントにデータを送ります。この時、Acceptヘッダーに 'application/json' を指定します

また、以下の部分で validate で設定したエラーメッセージを出しています。(ここでは name がない場合に Name is required というエラーメッセージを出しています)
他にも様々なバリデーションが設定できるので、Felteの ValidationValidators のドキュメントをご確認ください。

1<label for="name">Name*</label>
2<input id="name" name="name" aria-describedby="error-name-required" />
3{#if $errors.name}
4  <span id="error-name-required" aria-live="assertive">
5    {$errors.name}
6  </span>
7{/if}

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

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

sveltekit-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. @types/grecaptchaのインストール

後述の処理で必要になるため、@types/grecaptcha をインストールしておきます。

npm install --save-dev @types/grecaptcha
# or
yarn add -D @types/grecaptcha

5-4. SvelteKitでの設定

最後にSvelteKitでの設定を行います。
まず、環境変数に PUBLIC_RECAPTCHA_SITE_KEY を追加し、5-1で取得したサイトキーを設定します。以下を、実際の値で置き換えて定義してください。

.env
PUBLIC_NEWT_FORM_ENDPOINT=https://xxxxxx.form.newt.so/v1/xxxxxx
PUBLIC_RECAPTCHA_SITE_KEY=xxxx

3-2で作成した src/routes/contact/+page.svelte を以下のように修正します。

src/routes/contact/+page.svelte
1<script lang="ts">
2  import { PUBLIC_NEWT_FORM_ENDPOINT, PUBLIC_RECAPTCHA_SITE_KEY } from '$env/static/public'
3  import { createForm } from 'felte'
4
5  type FormValues = {
6    name: string
7    email: string
8    message: string
9    googleReCaptchaToken: string
10  }
11
12  const { form, errors } = createForm<FormValues>({
13    validate: (values) => {
14      const errors: Record<string, string> = {}
15      if (!values.name) {
16        errors.name = 'Name is required'
17      }
18      return errors
19    },
20    onSubmit: async (values) => {
21      grecaptcha.ready(() => {
22        grecaptcha
23          .execute(PUBLIC_RECAPTCHA_SITE_KEY, { action: 'submit' })
24          .then(async (token) => {
25            values.googleReCaptchaToken = token
26
27            const formData = new FormData()
28            Object.entries(values).forEach(([key, value]) => {
29              formData.append(key, value)
30            })
31
32            await sendRequest(formData)
33          })
34      })
35
36      const sendRequest = async (formData: FormData) => {
37        await fetch(PUBLIC_NEWT_FORM_ENDPOINT, {
38          method: 'POST',
39          body: formData,
40          headers: {
41            Accept: 'application/json'
42          }
43        })
44      }
45    }
46  })
47</script>
48
49<svelte:head>
50  <title>Newt・SvelteKitフォーム</title>
51  <meta name="description" content="NewtとSvelteKitを利用した問い合わせフォームです" />
52  <script
53    src="https://www.google.com/recaptcha/api.js?render={PUBLIC_RECAPTCHA_SITE_KEY}&hl=ja"
54    async
55    defer
56  ></script>
57</svelte:head>
58
59<div>
60  <h1>Contact us</h1>
61  <form use:form>
62    <label for="name">Name*</label>
63    <input id="name" name="name" aria-describedby="error-name-required" />
64    {#if $errors.name}
65      <span id="error-name-required" aria-live="assertive">
66        {$errors.name}
67      </span>
68    {/if}
69    <label for="email">Email</label>
70    <input id="email" name="email" type="email" />
71    <label for="message">Message</label>
72    <textarea id="message" name="message" />
73    <button type="submit">Submit</button>
74  </form>
75</div>

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

  • googleReCaptchaToken というフィールド名でトークンを送信します(他の名前で送信した場合、正しく動作しないので注意してください)
  • サイトキーを使用して、reCAPTCHAのJavaScript APIを読み込みます
  • reCAPTCHAのJavaScript APIの読み込みでは hl クエリに ja を設定しているため、問い合わせフォームの右下に表示されるreCAPTCHAのバッジが日本語になります。他の言語で設定したい方は、Googleの Language Codes のドキュメントを参考に設定してください

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

form-tutorial15.jpg

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

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

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

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

送信成功ページとして、以下を用意します。
src/routes/thanks/+page.svelte を作成します。

src/routes/thanks/+page.svelte
1<svelte:head>
2  <title>Thank you</title>
3  <meta name="description" content="問い合わせの送信が成功しました" />
4</svelte:head>
5
6<div class="Result">
7  <h1>Thank you!</h1>
8  <div>
9    <a href="/contact">Back to Previous Page</a>
10  </div>
11</div>

送信失敗ページとして、以下を用意します。
src/routes/error/+page.svelte を作成します。

src/routes/error/+page.svelte
1<svelte:head>
2  <title>Error</title>
3  <meta name="description" content="問い合わせの送信が失敗しました" />
4</svelte:head>
5
6<div class="Result">
7  <h1>Error!</h1>
8  <div>
9    <a href="/contact">Back to Previous Page</a>
10  </div>
11</div>

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

src/routes/contact/+page.svelte を以下のように修正します。

src/routes/contact/+page.svelte
  import { goto } from '$app/navigation'
  import { PUBLIC_NEWT_FORM_ENDPOINT, PUBLIC_RECAPTCHA_SITE_KEY } from '$env/static/public'
  import { createForm } from 'felte'

(省略)

    onSubmit: async (values) => {
      grecaptcha.ready(() => {
        grecaptcha
          .execute(PUBLIC_RECAPTCHA_SITE_KEY, { action: 'submit' })
          .then(async (token) => {
            values.googleReCaptchaToken = token

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

            await sendRequest(formData)
            try {
              const response = await sendRequest(formData)
              if (response.ok) {
                goto('/thanks')
              } else {
                goto('/error')
              }
            } catch (err) {
              goto('/error')
            }
          })
      })

(省略)

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

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

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

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

NewtMade in Newt