Tatami Study の教室サイトを Cloudflare Workers に移しました
2026年8月、プログラミング教室 Tatami Study の Webサイトの配信先を Vercel から Cloudflare Workers に切り替えました。体験授業の予約機能が Workers 上で動かなかった原因と、その直し方を書きます。
2026年8月24日に、プログラミング教室 Tatami Study の Webサイト(programming.miraiport.com)の配信先を Vercel から Cloudflare Workers に切り替えました。サイトは Next.js で作っていて、Workers 上では OpenNext の Cloudflare アダプタを使って動かしています。
ほとんどのページはそのまま動きましたが、1か所だけ動かないところがありました。体験授業の予約で使っている、空き時間の取得です。
予約の空き時間が取れない
予約画面では、教室の Google カレンダーから空き時間を読み出しています。Google の API を呼ぶにはサービスアカウントでアクセストークンを取る必要があり、移行前は google-auth-library を使っていました。
これが Workers 上では Could not refresh access token で失敗し、空き時間の API がエラーを返していました。
Web Crypto で書き直す
サービスアカウントのトークン取得は、やっていること自体は単純です。
- 「誰が・どの権限で」を書いた JWT を作る
- 秘密鍵で RS256 署名する
- Google のトークンエンドポイントに送って、アクセストークンと交換する
署名は Workers にもある Web Crypto API でできるので、ライブラリをやめて、この部分だけを自分で書くことにしました。
const key = await crypto.subtle.importKey(
"pkcs8",
pemToDer(privateKey),
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
key,
new TextEncoder().encode(`${header}.${claim}`)
);
アクセストークンは1時間有効なので、期限の60秒前までは使い回しています。カレンダー API を呼ぶ側のコードは変えていません。
もう1つの落とし穴
書き直したあとも、今度は atob() called with invalid base64-encoded data で失敗し続けました。
原因はコードではなく、Workers に登録した秘密鍵の値でした。設定ファイルから値をまとめて登録したときに、ファイル内のコメント行と空行が、直前の値の末尾にくっついたまま登録されていたのです。秘密鍵の末尾に日本語のコメントが付いていれば、base64 として読めないのは当然でした。
設定ファイルを読むときは KEY=値 の形の行だけを拾い、コメント行と空行は読み飛ばすようにして、値を入れ直しました。
切り替える前に確かめたこと
切り替えの前に、移行前と移行後で同じ結果が返ってくるかを確かめました。
| 確かめたもの | 結果 |
|---|---|
| 全ページの表示テキスト | 一致 |
| 予約の空き時間 API | レスポンスがバイト単位で一致 |
| 画像の最適化 | 動作 |
こうした確認をしたうえで、本番のドメインを切り替えました。