NewtとNext.jsを利用して、問い合わせフォームを作成する
Table of contents
- 記事内で使用している主なソフトウェアのバージョン
- 前提条件
- 概要
- 1. Form Appを作成する
- 1-1. Form Appの追加
- 1-2. フォームの作成
- 2. Next.jsでシンプルな問い合わせフォームを作成する
- 2-1. 環境変数の設定
- 2-2. HTMLフォームの追加
- 2-3. 自動返信メールの設定
- 2-4. 受信通知メールの設定
- 2-5. 挙動の確認
- 3. バリデーションを追加する
- 3-1. input要素の属性を利用したバリデーション
- 3-2. JavaScriptを利用したバリデーション
- 4. リダイレクトを追加する
- 4-1. 送信成功ページ・送信失敗ページを用意する
- 4-2. リダイレクトを設定する
- 5. スパム対策を実装する
- 5-1. サイトキーとシークレットキーの取得
- 5-2. シークレットキーの登録
- 5-3. 環境変数の設定
- 5-4. Next.jsでの設定
- 5-5. エラーが発生してしまう場合
- 次のステップ
このチュートリアルでは、Newtの Form App と Next.js を利用して、問い合わせフォームを作成する手順を紹介します。
記事内で使用している主なソフトウェアのバージョン
- Next.js(
next
): 13.5.6 - React Hook Form(
react-hook-form
): 7.47.0 - React Google Recaptcha V3(
react-google-recaptcha-v3
): 1.10.1
前提条件
- Next.jsのプロジェクトを作成済みであること
Next.jsのセットアップについて知りたい場合は、以下のドキュメントをご確認ください。
- Next.jsの Getting Started のドキュメント
- NewtとNext.jsを利用してブログを作成する
概要
Next.jsで問い合わせフォームを作成し、Newtで問い合わせデータを確認できるようにします。
はじめに、自動返信・受信通知のついたシンプルなフォームを作成し、その後でバリデーション・リダイレクト・スパム対策の処理を実装しながら、カスタマイズの方法を学習していきます。
※ ここではスタイルについては扱いません。スタイルについて確認したい方は、問い合わせ用のテンプレート newt-starter-nextjs-contact を用意しているので、そちらのコードをご確認ください。
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. Next.jsでシンプルな問い合わせフォームを作成する
続いて、Next.jsでシンプルな問い合わせフォームを作成しましょう。
ここでは /contact/simple
というパスにアクセスした時に、問い合わせフォームを表示するようにします。
2-1. 環境変数の設定
Next.jsには環境変数のビルトインサポートがあり、.env.local
を使用して、環境変数をロードできます。Next.jsの環境変数について、詳細は Environment Variables のドキュメントをご確認ください。
ここでは、.env.local
ファイルを作成し、2-1で確認したエンドポイントの値を定義します。以下を、実際の値で置き換えて定義してください。
1NEXT_PUBLIC_NEWT_FORM_ENDPOINT=https://xxxxx.form.newt.so/v1/xxxxx
上記のように定義しておくと、process.env.NEXT_PUBLIC_NEWT_FORM_ENDPOINT
として利用できるようになります。
2-2. HTMLフォームの追加
app/contact/simple/page.tsx
を作成し、以下のように記述します。
form
の action
属性にはエンドポイントの環境変数を指定します。
1export default function Page() {
2 return (
3 <>
4 <h1>Contact us</h1>
5 <form action={process.env.NEXT_PUBLIC_NEWT_FORM_ENDPOINT} method="post">
6 <label htmlFor="name">Name</label>
7 <input id="name" name="name" />
8 <label htmlFor="email">Email</label>
9 <input id="email" name="email" type="email" />
10 <label htmlFor="message">Message</label>
11 <textarea id="message" name="message"></textarea>
12 <button type="submit">Submit</button>
13 </form>
14 </>
15 )
16}
各属性について説明します。
label要素のhtmlFor属性と、input要素・textarea要素のid属性
label要素 の for
属性は、JSXでは htmlFor
として記述します。input要素 や textarea要素 の id
属性と一致するように設定してください。
※ 詳細は、Reactの アクセシブルなフォーム のドキュメントをご確認ください。
input要素・textarea要素のname属性
input要素・textarea要素のnameで設定された値をもとに、投稿データのスキーマが決まります。例えば上記の例では、name
・email
・message
フィールドを持ったスキーマが作成されます。
詳細については、フィールドの仕様 のドキュメントをご確認ください。
input要素のtype属性
お好みの入力形式に応じて、inputの型 を選択してください。デフォルトでは text
となります。
これで http://localhost:3000/contact/simple
にアクセスすると、以下のような問い合わせフォームが作成できました。
※ スタイルについてはお好みで設定してください
2-3. 自動返信メールの設定
自動返信メール の設定をします。自動返信メールを設定することで、投稿を送信したエンドユーザーに対して、設定した内容のメールを自動で送信できます。
※ Newtは自動返信メールの送信先メールアドレスを検出するために email
フィールドを使います。input要素のname属性に email
を指定し、エンドユーザーのメールアドレスが入力されるようにしてください。
「App設定」から該当のフォームをクリックし、「メール設定」を選択します。
「自動返信メールを有効にする」にチェックを入れ、件名と本文を入力し、「保存」をクリックします。
自動返信メールでは {submission.name}
のように記述することで、メールの件名や本文に、投稿データの内容を埋め込めます。
2-2で、input要素・textarea要素のname属性に name
・email
・message
を指定したので、ここでは {submission.name}
・{submission.email}
・{submission.message}
を利用できます。
2-4. 受信通知メールの設定
受信通知メール の設定をします。受信通知メールを設定することで、エンドユーザーから新しいメッセージを受信した際に、メールで通知を受け取れます。
「App設定」から該当のフォームをクリックし、「メール設定」を選択します。
「受信通知メールを有効にする」にチェックを入れ、通知を受け取るメールアドレス・件名・本文を設定し、「保存」をクリックします。
受信通知メールでも、自動返信メールと同様に、投稿データの内容を埋め込めます。
2-5. 挙動の確認
これで、シンプルな問い合わせフォームを作成できました。
実際に動かして、挙動を確認してみましょう。
まず、開発サーバーを立ち上げて、問い合わせフォームを表示します。ここでは http://localhost:3000/contact/simple
にアクセスします。
適当な内容を入力して送信すればよいですが、設定した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を利用することで、より柔軟なバリデーションを実行できます。
ここでは、バリデーションのタイミングを変え、さらにエラーメッセージをカスタマイズしてみましょう。
デフォルトのThanksページ・Errorページへのリダイレクトや カスタムリダイレクト は行われなくなりますので、ご注意ください。
ここでは React Hook Form を利用してバリデーションを行います。まずは react-hook-form をインストールしましょう。
npm install react-hook-form
# or
yarn add react-hook-form
続いて、以下のように app/contact/react-hook-form/page.tsx
を作成します。
1'use client'
2import { useForm } from 'react-hook-form'
3
4type FormValues = {
5 name: string
6 email: string
7 message: string
8}
9
10export default function Page() {
11 const {
12 register,
13 handleSubmit,
14 formState: { errors },
15 } = useForm<FormValues>({ mode: 'onChange' })
16
17 const onSubmit = handleSubmit(async (data) => {
18 const formData = new FormData()
19 Object.entries(data).forEach(([key, value]) => {
20 formData.append(key, value)
21 })
22
23 await fetch(process.env.NEXT_PUBLIC_NEWT_FORM_ENDPOINT ?? '', {
24 method: 'POST',
25 body: formData,
26 headers: {
27 Accept: 'application/json',
28 },
29 })
30 })
31
32 return (
33 <>
34 <h1>Contact us</h1>
35 <form onSubmit={onSubmit}>
36 <label htmlFor="name">Name*</label>
37 <input
38 id="name"
39 {...register('name', { required: 'Name is required' })}
40 aria-describedby="error-name-required"
41 />
42 {errors?.name && (
43 <span id="error-name-required" aria-live="assertive">
44 {errors.name.message}
45 </span>
46 )}
47 <label htmlFor="email">Email</label>
48 <input id="email" {...register('email')} />
49 <label htmlFor="message">Message</label>
50 <textarea id="message" {...register('message')}></textarea>
51 <button type="submit">Submit</button>
52 </form>
53 </>
54 )
55}
以下のことを行っています。
- 入力値の型として、
FormValues
を定義し、useForm にジェネリクスとして渡します useForm
にmode
という引数を渡しています。これはどのタイミングでバリデーションを行うか制御しています。ここではonChange
を指定し、変更のたびにバリデーションを実行します- 入力されたフォームのデータは handleSubmit 関数で
data
として利用できます - FormData を利用して、送信するデータ
formData
を作成します - フェッチ API を利用して、Form Appのエンドポイントにデータを送ります。この時、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)}
http://localhost:3000/contact/react-hook-form
にアクセスして、以下のことを確認します。
- 入力変更があったタイミングでバリデーションが実行されること
name
が入力されていない場合にName is required
というエラーメッセージが出ること
4. リダイレクトを追加する
次に、3-2で作成したフォームにリダイレクトを追加してみましょう。
送信が成功した場合のページと、失敗した場合のページを用意して、送信結果に応じてリダイレクトを行います。
4-1. 送信成功ページ・送信失敗ページを用意する
送信成功ページとして、以下を用意します。
app/thanks/page.tsx
を作成します。
1'use client'
2import { useRouter } from 'next/navigation'
3
4export default function Thanks() {
5 const router = useRouter()
6
7 return (
8 <>
9 <h1>Thank you!</h1>
10 <div>
11 <button type="button" onClick={() => router.back()}>
12 Back to Previous Page
13 </button>
14 </div>
15 </>
16 )
17}
送信失敗ページとして、以下を用意します。
app/error/page.tsx
を作成します。
1'use client'
2import { useRouter } from 'next/navigation'
3
4export default function Error() {
5 const router = useRouter()
6
7 return (
8 <>
9 <h1>Error!</h1>
10 <div>
11 <button type="button" onClick={() => router.back()}>
12 Back to Previous Page
13 </button>
14 </div>
15 </>
16 )
17}
4-2. リダイレクトを設定する
app/contact/react-hook-form/page.tsx
を以下のように修正します。
'use client'
import { useRouter } from 'next/navigation'
import { useForm } from 'react-hook-form'
type FormValues = {
name: string
email: string
message: string
}
export default function Page() {
const router = useRouter()
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({ mode: 'onChange' })
const onSubmit = handleSubmit(async (data) => {
const formData = new FormData()
Object.entries(data).forEach(([key, value]) => {
formData.append(key, value)
})
await fetch(process.env.NEXT_PUBLIC_NEWT_FORM_ENDPOINT ?? '', {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
},
})
try {
const response = await fetch(
process.env.NEXT_PUBLIC_NEWT_FORM_ENDPOINT ?? '',
{
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
},
},
)
if (response.ok) {
router.push('/thanks')
} else {
router.push('/error')
}
} catch (err) {
router.push('/error')
}
})
return (
(省略)
)
}
送信が成功した場合(response.ok がtrueの場合)は /thanks
にリダイレクトし、送信成功ページを表示しています。
送信が失敗した場合(response.ok がfalseの場合、またはエラーが発生した場合)は /error
にリダイレクトし、送信失敗ページを表示しています。
これでリダイレクトを追加することができました。
5. スパム対策を実装する
最後に、スパム対策 として Google reCAPTCHA v3 の設置方法について説明します。
5-1. サイトキーとシークレットキーの取得
Google reCAPTHAのコンソール にアクセスし、新しいサイトを登録します。
reCAPTCHAタイプは reCAPTCHA v3
を選択します。
ここではローカル環境でのテストを行うので、ドメインには localhost
を追加します。本番環境で設定したい場合は、reCAPTCHAを導入したいサイトのドメインを設定してください。
全ての項目を入力して「送信」をクリックすると、サイトキーとシークレットキーが表示されます。
5-2. シークレットキーの登録
Newtの管理画面にアクセスし、「App設定」を開きます。該当のフォームをクリックし、「フォーム設定」を選択します。
「スパム対策」のセクションより、「Google reCAPTCHA v3を有効にする」にチェックを入れ、5-1で取得したシークレットキーを貼り付けて保存します。
5-3. 環境変数の設定
5-1で確認したサイトキーを NEXT_PUBLIC_RECAPTCHA_SITE_KEY
として環境変数に追加します。
NEXT_PUBLIC_NEWT_FORM_ENDPOINT=https://xxxxx.form.newt.so/v1/xxxxx
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=xxxxxxxxxxxxxxx
5-4. Next.jsでの設定
最後にNext.jsでの設定を行います。ここでは React Google Recaptcha V3 を用いて、設定します。まずはReact Google Recaptcha V3をインストールしましょう。
npm install react-google-recaptcha-v3
# or
yarn add react-google-recaptcha-v3
React Google Recaptcha V3を利用するためには、該当のコンポーネントを GoogleReCaptchaProvider
でラップする必要があるので app/contact/recaptcha/layout.tsx
を作成します。
※ ここでは app/contact/recaptcha/page.tsx
にフォームを作成するとします。
以下のように作成します。
※ GoogleReCaptchaProvider
に設定できるオプションの詳細は React Google Recaptcha V3 をご確認ください。
1'use client'
2import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'
3
4export default function Layout({ children }: { children: React.ReactNode }) {
5 return (
6 <GoogleReCaptchaProvider
7 reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? ''}
8 language="ja"
9 >
10 {children}
11 </GoogleReCaptchaProvider>
12 )
13}
ここでは language
に ja
を設定しているため、問い合わせフォームの右下に表示されるreCAPTCHAのバッジが日本語になります。他の言語で設定したい方は、Googleの Language Codes のドキュメントを参考に設定してください。
最後に送信データに googleReCaptchaToken
を追加します。
app/contact/recaptcha/page.tsx
を、以下のように作成します。
1'use client'
2import { useRouter } from 'next/navigation'
3import { useGoogleReCaptcha } from 'react-google-recaptcha-v3' // 1
4import { useForm } from 'react-hook-form'
5
6type FormValues = {
7 name: string
8 email: string
9 message: string
10 googleReCaptchaToken: string
11}
12
13export default function Page() {
14 const router = useRouter()
15 const {
16 register,
17 handleSubmit,
18 formState: { errors },
19 } = useForm<FormValues>({ mode: 'onChange' })
20 const { executeRecaptcha } = useGoogleReCaptcha()
21
22 const onSubmit = handleSubmit(async (data) => {
23 if (!executeRecaptcha) return // 2
24 const token = await executeRecaptcha('submit')
25 data.googleReCaptchaToken = token // 3
26
27 const formData = new FormData()
28 Object.entries(data).forEach(([key, value]) => {
29 formData.append(key, value)
30 })
31
32 try {
33 const response = await fetch(
34 process.env.NEXT_PUBLIC_NEWT_FORM_ENDPOINT ?? '',
35 {
36 method: 'POST',
37 body: formData,
38 headers: {
39 Accept: 'application/json',
40 },
41 },
42 )
43
44 if (response.ok) {
45 router.push('/thanks')
46 } else {
47 router.push('/error')
48 }
49 } catch (err) {
50 router.push('/error')
51 }
52 })
53
54 return (
55 <>
56 <h1>Contact us</h1>
57 <form onSubmit={onSubmit}>
58 <label htmlFor="name">Name*</label>
59 <input
60 id="name"
61 {...register('name', { required: 'Name is required' })}
62 aria-describedby="error-name-required"
63 />
64 {errors?.name && (
65 <span id="error-name-required" aria-live="assertive">
66 {errors.name.message}
67 </span>
68 )}
69 <label htmlFor="email">Email</label>
70 <input id="email" {...register('email')} />
71 <label htmlFor="message">Message</label>
72 <textarea id="message" {...register('message')}></textarea>
73 <button type="submit">Submit</button>
74 </form>
75 </>
76 )
77}
以下のことを行っています。
- useGoogleReCaptcha をインポートします
executeRecaptcha
のnullチェックをし、利用可能か確認しますgoogleReCaptchaToken
というフィールド名でトークンを送信します
これでGoogle reCAPTCHA v3によるスパム対策が実装できました。
もし、バッジを非表示にしたい場合、Googleの reCAPTCHA バッジを非表示にします。どうすればよいですか? のドキュメントをご確認ください。
5-5. エラーが発生してしまう場合
reCAPTCHAを利用したフォームでエラーが発生してしまう場合は、まずはどのようなエラーが発生しているかレスポンスを確認しましょう。
以下のようなログを追加すると、返却されているデータを確認できます。
const res = await response.json()
console.log(res)
正常にフォームが送信されている場合は以下のようなデータが返却されます。
{
"status": 200,
"success": true
}
一方、エラーが発生した場合は、エラー内容が返却されます。
例えば、googleReCaptchaToken
というフィールド名でトークンが送信されていない場合、以下のようなエラーが返却されます。
{
"status": 400,
"code": "InvalidGoogleRecaptchaToken"
"message": "The google reCAPTHCHA token is invalid."
}
エラー内容の一覧は Error types に記載がありますので、どの code
が表示されているか確認して、問題の原因を確認してみてください。
よくある問題としては、以下のようなものがあります。ご注意ください。
- 管理画面でシークレットキーが登録されていない(InvalidGoogleRecaptchaSecret)
googleReCaptchaToken
というフィールド名で、トークンが送信されていない(InvalidGoogleRecaptchaToken)- トークンの作成から2分以上経過し、期限が切れたトークンを送信している(InvalidGoogleRecaptchaToken)
- Google reCAPTHAのコンソールに、対象のドメインを追加していない(SpamDetected)
次のステップ
Form Appでは、他にもさまざまな機能があるので、興味のある方はぜひ以下のドキュメントもご覧ください。