お問い合わせフォームの送信がGoogle Apps Script(GAS)に届かない、という問題にハマった。コードを見ると一見正しそうなのに、GASのログには何も残っていない。
問題のコード
const postparam = {
method: "POST",
mode: "no-cors",
"Content-Type": "application/x-www-form-urlencoded", // ← ここがバグ
body: JSON.stringify(forms)
};
await fetch(url, postparam);
何がおかしかったか
"Content-Type" がトップレベルに書かれている。fetch() のオプションとしてHTTPヘッダーを指定するには headers オブジェクトの中に書く必要がある。
// ❌ 無効(fetchのオプションとして無視される)
{
mode: "no-cors",
"Content-Type": "application/x-www-form-urlencoded",
}
// ✅ 有効
{
mode: "no-cors",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}
トップレベルに書いた "Content-Type" はfetchに無視される。つまりヘッダーが送信されていなかった。
no-corsとContent-Typeのもう一つの問題
さらに複雑なのが mode: "no-cors" の動作だ。
no-cors モードでは、リクエストは「シンプルリクエスト」として送られる。シンプルリクエストで許可されるContent-Typeは:
text/plainapplication/x-www-form-urlencodedmultipart/form-data
の3つだけ。application/json は許可されない。
ところがこのコードはContent-Typeに application/x-www-form-urlencoded を指定しながら、bodyは JSON.stringify() でJSON文字列を送っている。宣言と内容が矛盾している。
結果的に動いた方法
別のサイトで同じGASに送信するコードが動いていたので中を見たら、なんとバグを含んだまま動いていた。
// Content-Typeがトップレベルにあるバグコード(でも動いている)
{
method: "POST",
mode: "no-cors",
"Content-Type": "application/x-www-form-urlencoded", // 無視される
body: JSON.stringify(forms)
}
ヘッダーが送信されないため、ブラウザはデフォルトで text/plain;charset=UTF-8 を使う。GAS側では e.postData.contents でJSON文字列をそのまま受け取り、JSON.parse() できる。結果的に動いていた。
正解はこう:
await fetch(url, {
method: 'POST',
mode: 'no-cors',
body: JSON.stringify({ name, email, message }),
// Content-Typeは省略 → text/plainになりGASが読める
});
GAS側:
function doPost(e) {
const data = JSON.parse(e.postData.contents);
// data.name, data.email, data.message が使える
}
まとめ
| 状況 | 結果 |
|---|---|
headers: { "Content-Type": "..." } と mode: "no-cors" | Content-Typeが application/x-www-form-urlencoded なら有効 |
トップレベルに "Content-Type" | 無視される → text/plain になる |
mode: "no-cors" でJSONボディ | Content-Typeを省略して text/plain にすれば届く |
GASに no-cors でJSONを送る場合は、Content-Typeを書かずに body: JSON.stringify(...) だけにするのが一番シンプルだ。