静的サイトにお問い合わせフォームを実装する (GAS連携)
GitHub Pagesなどの静的サイトで、サーバーを持たずにお問い合わせフォームを実装する方法(Google Apps Script利用)をまとめました。
こんにちは!パン君です。
このポートフォリオサイトは GitHub Pages でホスティングしている静的なWebサイトです。
そしてお問い合わせを実装するにはメールに送信するための仕組みがいります。
API実装を常駐サーバーでトンネル通信をPlayit.ggを利用して行っていますが、数週間前からトンネルの作成が出来ずに困っています...
(公式には連絡したのですが...)
そこで今回は Google Apps Script (GAS) を簡易的なバックエンドAPIとして利用し、完全無料でメール送信機能を実装しました。
なぜ GAS (Google Apps Script) なのか
静的サイトでフォームを実装する方法はいくつかあります。
- Formspree などの外部サービスを使う
- 簡単だが、無料枠に制限があったり、送信後にサービスのページにリダイレクトされたりすることがある。
- Google Forms を埋め込む
- 一番簡単だが、デザインがサイトと統一できない。
- AWS Lambda + API Gateway
- 柔軟だが、構築が少し手間でお金がかかる可能性も。
- GAS (Google Apps Script)
- 無料で使える。
- JavaScriptで書ける。
GmailAppを使って簡単にメール送信できる。- Web APIとして公開できる。
今回は「自前でコントロールしたい」「コストをかけたくない」「JavaScriptで完結させたい」という理由から、 GAS を採用しました。
バックエンド実装 (GAS)
まずはバックエンドとなる GAS のコードです。
フォームから POST されたデータを受け取り、自分のメールアドレスに送信する処理を書きます。
ポイント
doPost(e)関数で POST リクエストを受け取る。e.postData.contentsをパースしてデータを取り出す。GmailApp.sendEmailでメールを送る。- CORS対策 として、適切なヘッダーを返す必要がある。
実際のコード(抜粋)はこんな感じです。
function doPost(e) {
// CORS対策ヘッダー
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Content-Type"
};
try {
// データのパース
let data = {};
if (e.postData && e.postData.contents) {
data = JSON.parse(e.postData.contents);
} else {
data = e.parameter;
}
// メール送信
const recipient = "my-email@example.com"; // 自分のアドレス
const subject = "お問い合わせがありました";
const body = `名前: ${data.name}\n内容: ${data.message}`;
GmailApp.sendEmail(recipient, subject, body, {
replyTo: data.email
});
// 成功レスポンス
const output = ContentService.createTextOutput(JSON.stringify({ result: "success" }));
output.setMimeType(ContentService.MimeType.JSON);
return output;
} catch (error) {
// エラーレスポンス
const output = ContentService.createTextOutput(JSON.stringify({ result: "error" }));
output.setMimeType(ContentService.MimeType.JSON);
return output;
}
}これを「ウェブアプリ」としてデプロイし、アクセス権限を「全員 (Anyone)」に設定することで、外部から叩ける API URL が発行されます。
フロントエンド実装 (JavaScript)
次にこの API を叩くフロントエンド側の処理です。fetch API を使って非同期送信を行います。
CORSのエラーを回避するコツ
GAS に対して application/json で POST すると、CORS (Cross-Origin Resource Sharing) のプリフライトリクエスト (OPTIONS) が発生し、GAS がうまく返答できずにエラーになることがあります。
これを回避するためにあえて text/plain としてデータを送信する方法を採用しました。
const GAS_API_URL = "https://script.google.com/macros/s/xxxx/exec";
form.addEventListener("submit", async (e) => {
e.preventDefault();
// 入力データの収集
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
try {
// text/plain で送信するのがコツ
const response = await fetch(GAS_API_URL, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "text/plain;charset=utf-8",
},
});
const resJson = await response.json();
if (resJson.result === "success") {
alert("送信しました!");
form.reset();
}
} catch (err) {
alert("送信に失敗しました");
}
});GAS側では JSON.parse(e.postData.contents) で受け取ることで、問題なく JSON として扱えます。
デザインの調整
機能だけでなく見た目もサイトのトーン&マナーに合わせました。
- 必須項目のバッジ表示
- 送信ボタンのローディングアニメーション
- スマホでの入力しやすさ(
input type="email"やtype="tel"の活用)
特に送信ボタンは押した後に「送信中...」であることが視覚的に分かるように、CSSアニメーションでスピナーを表示するようにしました。
ユーザーが何度も連打してしまうのを防ぐ効果もあります。
まとめ
サーバーレスな環境でも GAS を組み合わせることで本格的なお問い合わせフォームが実装できました。
- GAS は無料かつ手軽なバックエンドとして優秀。
fetchのContent-Typeを工夫することで CORS 問題を回避できる。
これでポートフォリオサイトから直接お仕事の相談を受け付ける準備が整いました!
コメントを読み込み中...