静的サイトの問い合わせフォームをGoogleフォームに直接送る
このブログの「お問い合わせ」ページは、見た目はサイトの一部として作った自前のフォームですが、押した先で待っているのはGoogleフォームです。サーバーを持たない静的サイトなので、送られた内容を受け取る場所が自分の側にありません。どこかに預ける必要があり、それをGoogleフォームにしました。最初は素直に埋め込んで、7分でやめて、自作フォームから直接送る形に作り直した。その理由と実装、そして分かっていて放置している弱点を書きます。
サーバーのない静的サイトでも、フォームの送信先をGoogleフォームの受け口にすれば、無料で問い合わせを受け取れます。
静的サイトには「受け取る場所」がない
このブログはAstroで作った静的サイトで、配信しているのはHTMLと画像だけです。フォームの <form> 要素は、送信先(action)にデータを送るところまではブラウザがやってくれますが※1、送られた先で受け止めて保存する処理は、こちらで用意しなければ存在しません。
候補は3つありました。
| 方法 | 利点 | 選ばなかった理由 |
|---|---|---|
| フォーム受付サービス | 導入が簡単、スパム対策込みのものもある | 無料枠や規約を比べる前に、手元のGoogleアカウントで済む方法を先に取った |
| Cloudflare Workersで受けてメール送信 | 自由度が高い | 受け口のコード、メール送信の設定、保存先まで自分で持つことになる |
| Googleフォームに送る | 無料。回答はGoogleフォームの回答タブに溜まり、スプレッドシートにも出せる※2 | 後述の弱点がある |
3つ目を選びました。決め手は「保存と一覧を作らなくてよい」ことです。回答が届く場所と、それを見る画面が最初からある。問い合わせが月に数件のブログには、これで足ります。
埋め込みは7分でやめた
Googleフォームには、フォームを <iframe> として自分のページに埋め込む機能があります。8月7日の13時45分、まずそれを置きました。動くことは動きます。ただ、余白の多い紙白のページの真ん中に、Googleフォームの見た目がそのまま四角く貼り付いている。フォントも余白も色も、周りと合っていませんでした。iframeの中身はGoogle側のスタイルで描かれるので、外から合わせる手段がありません。
13時52分、埋め込みを外して、フォームをサイトのHTMLとCSSで自作し、送信先だけGoogleフォームにする形に変えました。7分間だけ、このブログにはGoogleフォームがそのまま貼ってあったことになります。
仕組み:送信先の受け口と、項目の番号
Googleフォームには、回答を受け付ける専用のURLがあります。フォームの公開URL(.../viewform)の末尾を formResponse に変えたものです。そこへ、質問ごとに決まった entry.数字 という名前で値を送ると、通常の回答として記録されます。
問題はその「数字」をどう知るかで、これはフォームの回答ページ(viewform)のHTMLを開いて、入力欄の name 属性から拾いました。このブログのフォームは、名前・メール・本文の3問で、それぞれこうなっています。
<form id="contact-form" action="https://docs.google.com/forms/d/e/.../formResponse" method="POST">
<input type="text" name="entry.2105104071" required /> <!-- お名前 -->
<input type="email" name="entry.327901856" required /> <!-- メールアドレス -->
<textarea name="entry.14527036" required></textarea> <!-- 内容 -->
<button type="submit">送信する</button>
</form>
そのまま <form> を送信させると、ブラウザがGoogleフォームの「回答を記録しました」ページへ移動してしまう。それを避けるために、送信はJavaScriptで横取りして、fetch で裏側から送っています。フォームの中身は FormData でそのまま束ねます※3。
form.addEventListener('submit', async (e) => {
e.preventDefault();
if (!form.reportValidity()) return;
const data = new FormData(form);
await fetch(form.action, { method: 'POST', mode: 'no-cors', body: data });
form.hidden = true; // 自作の完了メッセージに切り替える
done.hidden = false;
});
送ったあとは、ページを移動せずに、その場でサイトの見た目の完了メッセージを出します。読者から見れば、このブログの中で送信が完結しているように見えるはずです。
分かっていて、放置している弱点
きれいに動いているように書きましたが、この方式には自分で把握している弱点が3つあります。
1. 送信に成功したかどうかが分からない。 上のコードの mode: 'no-cors' がそれです。別のドメイン(Googleフォーム)へブラウザから送るには、このモードが要ります。そしてMDNの説明にあるとおり、no-corsを設定するとJavaScriptはレスポンスのヘッダーや本体にアクセスできなくなります※4。送ったあとに返ってくるのは中身の見えない応答だけで、成功したのか、Google側で弾かれたのかを、コードは知りようがありません。このブログでは「送れたものとして」完了画面を出しています。だから完了画面の下に、元のGoogleフォームへのリンクを小さく添えてあります。もし届いていなくても、そちらから送り直せるように。
2. Googleフォーム側の質問を変えると、静かに壊れる。 entry.2105104071 のような番号は、質問ごとに振られています。フォームの質問を作り直したり、消して足し直したりすると、この番号は変わります。そうなると、自作フォームは変わらず「送信しました」と表示し続けながら、実際には何も記録されない。エラーが出ないぶん、気づきにくい壊れ方です。対策として、フォームのソースコードの先頭に「質問を変えたらviewformのHTMLからentry IDを取り直すこと」とコメントを残していますが、それは未来の自分がそのコメントを読む前提の対策で、仕組みで防いではいません。
3. Googleが案内している使い方ではない。 formResponseへ直接送る方法は、Googleフォームのヘルプに載っている手順ではなく、公開されているHTMLの構造を読み取って組んだものです。仕様が変わって明日止まっても、文句を言う先がありません。それでも採用したのは、止まったときの被害が「問い合わせが届かなくなる」に限られ、それは元のGoogleフォームへのリンクで補えると考えたからです。
ついでに、スパム対策も1つだけ入れています。人間には見えない入力欄(ハニーポット)をフォームに置き、そこに何か入っていればボットとみなして送信せず、完了画面だけを見せる。凝った対策ではありませんが、フォームを機械的に埋めていく類の投稿には効くはずです。効き目は、問い合わせの数が増えてから確かめます。
プライバシーポリシーに書き足した
この記事を書く途中で気づいたことがあります。プライバシーポリシーの「お問い合わせで取得する情報」の項に、送信内容がGoogleフォームに保存されるという事実を書いていませんでした。読者から見れば、このブログのフォームに書いた内容がGoogleに送られていることは、フォームの見た目からは分かりません。8月18日付でその1段落を足しています。仕組みを記事にしようとして、自分の説明不足に気づく。書き残しておく効用は、こういうところにもありました。
参考文献・出典
※1 : 「<form>: フォーム要素 - HTML | MDN」 |MDN Web Docs https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/form
※2 : 「フォームの回答を表示、管理する - Google ドキュメント エディタ ヘルプ」 |Google ヘルプ https://support.google.com/docs/answer/139706?hl=ja
※3 : 「FormData - Web API | MDN」 |MDN Web Docs https://developer.mozilla.org/ja/docs/Web/API/FormData
※4 : 「Request: mode プロパティ - Web API | MDN」 |MDN Web Docs https://developer.mozilla.org/ja/docs/Web/API/Request/mode
よくある質問
静的サイトに問い合わせフォームを置くにはどうすればよいですか?
静的サイトにはフォームの送信を受け取るサーバーがないので、受け取り先を外に用意します。フォーム受付サービスを使う、サーバーレス関数でメールを送る、Googleフォームに送る、の3通りが代表的で、このブログはGoogleフォームを選びました。無料で、回答がGoogleフォームの回答タブに届き、スプレッドシートにもつなげられるからです。
自作フォームからGoogleフォームへ送信すると、成功したかどうか分かりますか?
分かりません。ブラウザから別ドメインのGoogleフォームへ送るにはfetchのno-corsモードを使う必要があり、このモードではJavaScriptがレスポンスの中身を読めないためです。このブログでは送信できたものとして完了画面を出し、万一のために元のGoogleフォームへのリンクも添えています。