NewtとAstroを利用して、問い合わせフォームを作成する
Table of contents
- 記事内で使用している主なソフトウェアのバージョン
- 前提条件
- 概要
- 1. Form Appを作成する
- 1-1. Form Appの追加
- 1-2. フォームの作成
- 2. Astroでシンプルな問い合わせフォームを作成する
- 2-1. HTMLフォームの追加
- 2-2. 自動返信メールの設定
- 2-3. 受信通知メールの設定
- 2-4. 挙動の確認
- 3. バリデーションを追加する
- 3-1. input要素の属性を利用したバリデーション
- 3-2. JavaScriptを利用したバリデーション
- 4. スパム対策を実装する
- 4-1. サイトキーとシークレットキーの取得
- 4-2. シークレットキーの登録
- 4-3. @types/grecaptchaのインストール
- 4-4. Astroでの設定
- 5. リダイレクトをカスタマイズする
- 5-1. 送信成功ページ・送信失敗ページを用意する
- 5-2. リダイレクトを設定する
このチュートリアルでは、Newtの Form App と Astro を利用して、問い合わせフォームを作成する手順を紹介します。
記事内で使用している主なソフトウェアのバージョン
- Astro(
astro
): 4.4.0 - @astrojs/react(
@astrojs/react
): 3.0.10 - React Hook Form(
react-hook-form
): 7.50.1
前提条件
- Astroのプロジェクトを作成済みであること
概要
Astroで問い合わせフォームを作成し、Newtで問い合わせデータを確認できるようにします。
はじめに、自動返信・受信通知のついたシンプルなフォームを作成し、その後でバリデーション・スパム対策・リダイレクトの処理を実装しながら、カスタマイズの方法を学習していきます。
※ ここではスタイルについては扱いません。スタイルについて確認したい方は、Next.jsのコードとなりますが、問い合わせ用のテンプレート newt-starter-nextjs-contact を用意しているので、そちらのコードをご確認ください。
Astroのセットアップについて知りたい場合は、以下のドキュメントをご確認ください。
- Astroの インストール のドキュメント
- NewtとAstroを利用してブログを作成する
1. Form Appを作成する
はじめに、NewtでForm Appを作成します。
1-1. Form Appの追加
「Appを追加」をクリックして「タイプを選択して追加」を選択します。
「Form App」を選択して「追加」をクリックします。
App名・App UID・Appアイコンを設定して「追加」をクリックします。
これで「Contact」という名前のForm Appが追加されました。
1-2. フォームの作成
次にフォームを作成します。「フォームを作成」ボタンをクリックし、名前を付けてフォームを作成します。
作成後に表示されるエンドポイントは、2-1で利用します。
2. Astroでシンプルな問い合わせフォームを作成する
続いて、Astroでシンプルな問い合わせフォームを作成しましょう。
ここでは /contact
というパスにアクセスした時に、問い合わせフォームを表示するようにします。
2-1. HTMLフォームの追加
src/pages/contact.astro
を作成し、以下のように記述します。
form
の action
属性に設定している https://xxxxxx.form.newt.so/v1/xxxxxx
の値については、1-2で確認したエンドポイントの値に置き換えてください。
1---
2import Layout from '../layouts/Layout.astro'
3---
4
5<Layout
6 title="Newt・Astroフォーム"
7 description="NewtとAstroを利用した問い合わせフォームです"
8>
9 <div>
10 <h1>Contact us</h1>
11 <form action="https://xxxxxx.form.newt.so/v1/xxxxxx" method="post">
12 <label for="name">
13 Name
14 <input id="name" name="name" />
15 </label>
16 <label for="email">
17 Email
18 <input id="email" name="email" type="email" />
19 </label>
20 <label for="message">
21 Message
22 <textarea id="message" name="message"></textarea>
23 </label>
24 <button type="submit">Submit</button>
25 </form>
26 </div>
27</Layout>
各属性について説明します。
label要素のfor属性と、input要素・textarea要素のid属性
label要素 の for
属性は、input要素 や textarea要素 の id
属性と一致するように設定します。
input要素・textarea要素のname属性
input要素・textarea要素のnameで設定された値をもとに、投稿データのスキーマが決まります。例えば上記の例では、name
・email
・message
フィールドを持ったスキーマが作成されます。
詳細については、フィールドの仕様 のドキュメントをご確認ください。
input要素のtype属性
お好みの入力形式に応じて、inputの型 を選択してください。デフォルトでは text
となります。
これで http://localhost:3000/contact
にアクセスすると、以下のような問い合わせフォームが作成できました。
※ スタイルについてはお好みで設定してください
2-2. 自動返信メールの設定
自動返信メール の設定をします。自動返信メールを設定することで、投稿を送信したエンドユーザーに対して、設定した内容のメールを自動で送信できます。
※ Newtは自動返信メールの送信先メールアドレスを検出するために email
フィールドを使います。input要素のname属性に email
を指定し、エンドユーザーのメールアドレスが入力されるようにしてください。
「App設定」から該当のフォームをクリックし、「メール設定」を選択します。
「自動返信メールを有効にする」にチェックを入れ、件名と本文を入力し、「保存」をクリックします。
自動返信メールでは {submission.name}
のように記述することで、メールの件名や本文に、投稿データの内容を埋め込めます。
2-1で、input要素・textarea要素のname属性に name
・email
・message
を指定したので、ここでは {submission.name}
・{submission.email}
・{submission.message}
を利用できます。
2-3. 受信通知メールの設定
受信通知メール の設定をします。受信通知メールを設定することで、エンドユーザーから新しいメッセージを受信した際に、メールで通知を受け取れます。
「App設定」から該当のフォームをクリックし、「メール設定」を選択します。
「受信通知メールを有効にする」にチェックを入れ、通知を受け取るメールアドレス・件名・本文を設定し、「保存」をクリックします。
受信通知メールでも、自動返信メールと同様に、投稿データの内容を埋め込めます。
2-4. 挙動の確認
これで、シンプルな問い合わせフォームを作成できました。
実際に動かして、挙動を確認してみましょう。
まず、開発サーバーを立ち上げて、問い合わせフォームを表示します。ここでは http://localhost:3000/contact
にアクセスします。
適当な内容を入力して送信すればよいですが、設定したEmail(name属性に email
を指定した入力値)に自動返信メールが届くので、ご自身のメールアドレスを入力するよう気をつけてください。
送信が成功すると、以下の画面が表示されます。
管理画面では、以下のようにデータが確認できます。
次に自動返信メールを確認します。以下のようなメールが飛んでいれば成功です。
最後に受信通知メールを確認します。以下のようなメールが飛んでいれば成功です。
これでシンプルなフォームの作成・自動返信メールの設定・受信通知メールの設定ができました。ここからはフォームのカスタマイズをしながら、より高度なフォームの作成方法を学んでいきましょう。
3. バリデーションを追加する
クライアントサイドのバリデーションについて、以下の順で紹介します。
- input要素の属性を利用したバリデーション
- JavaScriptを利用したバリデーション(React Hook Formを利用したバリデーション)
3-1. input要素の属性を利用したバリデーション
HTML5のフォームバリデーションを利用することで、JavaScriptなしでバリデーションを実行できます。よく使われるものについては HTML5のフォームバリデーション をご確認ください。
例えば、name
を必須にしたい場合は、以下のように required
を指定します。
<input id="name" name="name" required />
もし name
を指定せず、送信を実行した場合は、以下のように表示されます。
3-2. JavaScriptを利用したバリデーション
HTMLネイティブのフォームバリデーションでは足りない場合、JavaScriptを利用することで、より柔軟なバリデーションを実行できます。
ここでは、バリデーションのタイミングを変え、さらにエラーメッセージをカスタマイズしてみましょう。
ここではReact用のインテグレーションを追加し、React Hook Form を利用したバリデーションを行います。
3-2-1. React用インテグレーションの追加
はじめに astro add
コマンドを利用して、React用のインテグレーションを追加します。
詳細はAstroの @astrojs/react のドキュメントをご確認ください。
以下のどれかのコマンドを実行します。
npx astro add react
# or
yarn astro add react
コマンドを実行すると、以下の質問を聞かれるので、お好きな設定を選びましょう。
@astrojs/react
,@types/react-dom
,@types/react
,react-dom
,react
をインストールするか(ここではyes
を選択)- astro.config.mjsの変更を許可するか(ここでは
yes
を選択) - tsconfig.jsonの変更を許可するか(ここでは
yes
を選択)
yarnを利用した場合、以下のように表示されます。
$ yarn astro add react
✔ Resolving packages...
Astro will run the following command:
If you skip this step, you can always run it yourself later
╭─────────────────────────────────────────────────────────╮
│ yarn add @astrojs/react@^3.0.10 @types/react@^18.2.57 @types/react-dom@^18.2.19 react@^18.2.0 react-dom@^18.2.0 │
╰─────────────────────────────────────────────────────────╯
✔ Continue? … yes
✔ Installing dependencies...
Astro will make the following changes to your config file:
╭ astro.config.mjs ──────────────╮
│ import { defineConfig } from 'astro/config'; │
│ │
│ import react from "@astrojs/react"; │
│ │
│ // https://astro.build/config │
│ export default defineConfig({ │
│ integrations: [react()] │
│ }); │
╰───────────────────────╯
✔ Continue? … yes
success Added the following integration to your project:
- @astrojs/react
Astro will make the following changes to your tsconfig.json:
╭ tsconfig.json ─────────────╮
│ { │
│ "extends": "astro/tsconfigs/strict", │
│ "compilerOptions": { │
│ "jsx": "react-jsx", │
│ "jsxImportSource": "react" │
│ } │
│ } │
╰────────────────────╯
✔ Continue? … yes
success Successfully updated TypeScript settings
✨ Done in 49.82s.
3-2-2. React Hook Formの追加
ここでは React Hook Form を利用してバリデーションを行います。react-hook-form をインストールします。
npm install react-hook-form
# or
yarn add react-hook-form
3-2-3. ContactFormコンポーネントの追加
続いて、ContactFormコンポーネントを追加します。
以下のように src/components/ContactForm.tsx
を追加します。
※ fetch
の リソースとして設定されている https://xxxxxx.form.newt.so/v1/xxxxxx
の値については、1-2で確認したエンドポイントの値に置き換えてください。
1import { useForm } from 'react-hook-form'
2
3type FormValues = {
4 name: string
5 email: string
6 message: string
7}
8
9export default function ContactForm() {
10 const {
11 register,
12 handleSubmit,
13 formState: { errors },
14 } = useForm<FormValues>({ mode: 'onChange' })
15
16 const onSubmit = handleSubmit(async (data) => {
17 const formData = new FormData()
18 Object.entries(data).forEach(([key, value]) => {
19 formData.append(key, value)
20 })
21
22 await fetch('https://xxxxxx.form.newt.so/v1/xxxxxx', {
23 method: 'POST',
24 body: formData,
25 headers: {
26 Accept: 'application/json',
27 },
28 })
29 })
30
31 return (
32 <div>
33 <h1>Contact us</h1>
34 <form onSubmit={onSubmit}>
35 <label htmlFor="name">Name*</label>
36 <input
37 id="name"
38 {...register('name', { required: 'Name is required' })}
39 aria-describedby="error-name-required"
40 />
41 {errors?.name && (
42 <span id="error-name-required" aria-live="assertive">
43 {errors.name.message}
44 </span>
45 )}
46 <label htmlFor="email">Email</label>
47 <input id="email" {...register('email')} />
48 <label htmlFor="message">Message</label>
49 <textarea id="message" {...register('message')}></textarea>
50 <button type="submit">Submit</button>
51 </form>
52 </div>
53 )
54}
以下のことを行っています。
- 入力値の型として、
FormValues
を定義し、useForm にジェネリクスとして渡します useForm
にmode
という引数を渡しています。これはどのタイミングでバリデーションを行うか制御しています。ここではonChange
を指定し、変更のたびにバリデーションを実行します- 入力されたフォームのデータは handleSubmit 関数で
data
として利用できます - FormData を利用して、送信するデータ
formData
を作成します - フェッチ API を利用して、1-2で確認したエンドポイントにデータを送ります。この時、Acceptヘッダーに
'application/json'
を指定します
また、以下の部分で name
がない場合に Name is required
というエラーメッセージを出しています。
register メソッドを利用して、様々なバリデーションが設定できるので、ぜひお好きなバリデーションをお試しください。
1<label htmlFor="name">Name*</label>
2<input
3 id="name"
4 {...register('name', { required: 'Name is required' })}
5 aria-describedby="error-name-required"
6/>
7{errors?.name && (
8 <span id="error-name-required" aria-live="assertive">
9 {errors.name.message}
10 </span>
11)}
3-2-4. 作成したContactFormコンポーネントの利用
最後に src/pages/contact.astro
からContactFormコンポーネントをインポートして利用します。
1---
2import ContactForm from '../components/ContactForm'
3import Layout from '../layouts/Layout.astro'
4---
5
6<Layout
7 title="Newt・Astroフォーム"
8 description="NewtとAstroを利用した問い合わせフォームです"
9>
10 <ContactForm client:load />
11</Layout>
ContactFormコンポーネントをハイドレートするために clientディレクティブ を利用しています。client:load
を設定することで、ページロード時に即座にハイドレートが実行されます。
これでバリデーションの設定ができました。以下のことを確認します。
- 入力変更があったタイミングでバリデーションが実行されること
name
が入力されていない場合にName is required
というエラーメッセージが出ること
4. スパム対策を実装する
続いて、スパム対策 として Google reCAPTCHA v3 の設置方法について説明します。
4-1. サイトキーとシークレットキーの取得
Google reCAPTHAのコンソール にアクセスし、新しいサイトを登録します。
reCAPTCHAタイプは reCAPTCHA v3
を選択します。
ここではローカル環境でのテストを行うので、ドメインには localhost
を追加します。本番環境で設定したい場合は、reCAPTCHAを導入したいサイトのドメインを設定してください。
全ての項目を入力して「送信」をクリックすると、サイトキーとシークレットキーが表示されます。
4-2. シークレットキーの登録
Newtの管理画面にアクセスし、「App設定」を開きます。該当のフォームをクリックし、「フォーム設定」を選択します。
「スパム対策」のセクションより、「Google reCAPTCHA v3を有効にする」にチェックを入れ、4-1で取得したシークレットキーを貼り付けて保存します。
4-3. @types/grecaptchaのインストール
後述の処理で必要になるため、@types/grecaptcha をインストールしておきます。
npm install --save-dev @types/grecaptcha
# or
yarn add -D @types/grecaptcha
4-4. Astroでの設定
最後にAstroでの設定を行います。
3-1で作成した src/pages/contact.astro
を以下のように修正します。
また、以下の箇所については、正しい値に置き換えるよう注意してください。
https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key
で指定してるreCAPTCHA_site_key
には、4-1で取得したサイトキーを入力してくださいgrecaptcha.execute
で指定してるreCAPTCHA_site_key
には、4-1で取得したサイトキーを入力してくださいsendRequest
メソッドで送信しているhttps://xxxxxx.form.newt.so/v1/xxxxxx
は、1-2で確認したエンドポイントの値に置き換えてください
1---
2import Layout from '../layouts/Layout.astro'
3---
4
5<Layout
6 title="Newt・Astroフォーム"
7 description="NewtとAstroを利用した問い合わせフォームです"
8>
9 <div>
10 <h1>Contact us</h1>
11 <form id="form">
12 <label for="name">
13 Name
14 <input id="name" name="name" />
15 </label>
16 <label for="email">
17 Email
18 <input id="email" name="email" type="email" />
19 </label>
20 <label for="message">
21 Message
22 <textarea id="message" name="message"></textarea>
23 </label>
24 <textarea id="message" name="message"></textarea>
25 <button type="submit">Submit</button>
26 </form>
27 </div>
28</Layout>
29
30<script
31 is:inline
32 src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key&hl=ja"
33></script>
34<script>
35 const form = document.getElementById('form')
36 form?.addEventListener('submit', submitFormData)
37
38 function submitFormData(event: Event) {
39 event.preventDefault()
40 grecaptcha.ready(() => {
41 grecaptcha
42 .execute('reCAPTCHA_site_key', { action: 'submit' })
43 .then(async (token) => {
44 const target = event.target as typeof event.target & {
45 name: { value: string }
46 email: { value: string }
47 message: { value: string }
48 }
49
50 const data = {
51 name: target.name.value,
52 email: target.email.value,
53 message: target.message.value,
54 googleReCaptchaToken: token,
55 }
56
57 const formData = new FormData()
58 Object.entries(data).forEach(([key, value]) => {
59 formData.append(key, value)
60 })
61
62 await sendRequest(formData)
63 })
64 })
65 }
66
67 async function sendRequest(formData: FormData) {
68 return await fetch('https://xxxxxx.form.newt.so/v1/xxxxxx', {
69 method: 'POST',
70 body: formData,
71 headers: {
72 Accept: 'application/json',
73 },
74 })
75 }
76</script>
以下のことを行っています。
- サイトキーを使用して、reCAPTCHAのJavaScript APIを読み込みます。外部スクリプトであるため、is:inline ディレクティブを含めます
- reCAPTCHAのJavaScript APIの読み込みでは
hl
クエリにja
を設定しているため、問い合わせフォームの右下に表示されるreCAPTCHAのバッジが日本語になります。他の言語で設定したい方は、Googleの Language Codes のドキュメントを参考に設定してください googleReCaptchaToken
というフィールド名でトークンを送信します(他の名前で送信した場合、正しく動作しないので注意してください)
これでGoogle reCAPTCHA v3によるスパム対策が実装できました。
もし、バッジを非表示にしたい場合、.grecaptcha-badge { visibility: hidden; }
を適用すると非表示になりますが、この操作はGoogle公式のガイドに従った場合のみ許可されます。
詳細は、Googleの reCAPTCHA バッジを非表示にします。どうすればよいですか? のドキュメントをご確認ください。
5. リダイレクトをカスタマイズする
最後にフォーム送信後のリダイレクトをカスタマイズします。
送信が成功した場合のページと、失敗した場合のページを用意して、送信結果に応じてリダイレクトしてみましょう。
5-1. 送信成功ページ・送信失敗ページを用意する
送信成功ページとして、以下を用意します。
page/thanks.astro
を作成します。
1---
2import Layout from '../layouts/Layout.astro'
3---
4
5<Layout title="Thank you" description="問い合わせの送信が成功しました">
6 <div>
7 <h1>Thank you!</h1>
8 <div>
9 <a href="/contact">Back to Previous Page</a>
10 </div>
11 </div>
12</Layout>
送信失敗ページとして、以下を用意します。
page/error.astro
を作成します。
1---
2import Layout from '../layouts/Layout.astro'
3---
4
5<Layout title="Error" description="問い合わせの送信が失敗しました">
6 <div class="Result">
7 <h1>Error!</h1>
8 <div>
9 <a href="/contact">Back to Previous Page</a>
10 </div>
11 </div>
12</Layout>
5-2. リダイレクトを設定する
page/contact.astro
を以下のように修正します。
※ fetch
の リソースとして設定されている https://xxxxxx.form.newt.so/v1/xxxxxx
の値については、1-2で確認したエンドポイントの値に置き換えてください。
(省略)
function submitFormData(event: Event) {
event.preventDefault()
grecaptcha.ready(() => {
grecaptcha
.execute('reCAPTCHA_site_key', { action: 'submit' })
action: 'submit',
})
.then(async (token) => {
const target = event.target as typeof event.target & {
name: { value: string }
email: { value: string }
message: { value: string }
}
const data = {
name: target.name.value,
email: target.email.value,
message: target.message.value,
googleReCaptchaToken: token,
}
const formData = new FormData()
Object.entries(data).forEach(([key, value]) => {
formData.append(key, value)
})
await sendRequest(formData)
try {
const response = await sendRequest(formData)
if (response.ok) {
location.href = '/thanks'
} else {
location.href = '/error'
}
} catch (err) {
location.href = '/error'
}
})
})
}
(省略)
送信が成功した場合(response.ok がtrueの場合)は /thanks
にリダイレクトし、送信成功ページを表示しています。
送信が失敗した場合(response.ok がfalseの場合、またはエラーが発生した場合)は /error
にリダイレクトし、送信失敗ページを表示しています。
これでリダイレクトをカスタマイズすることができました。
以上で、すべてのステップが終了となります。
Form Appでは、他にもさまざまな機能があるので、興味のある方はぜひ以下のドキュメントもご覧ください。