Cloudflare Workers + better-auth + Stripe で個人技術ブログのメンバーシップ機能を実装した記録
Hugo から Next.js (Cloudflare Workers + OpenNext) へ移行した個人技術ブログに、Stripe ベースのメンバーシップ機能を載せた際の構成・実装手順・ハマりどころを記録します。Workers Free plan の CPU 制限との戦い、Stripe SDK の Workers 対応、SSG と動的ゲーティングの両立、本番化フォームの日本固有項目など、公式ドキュメントには断片的にしか書かれていない部分を中心に整理しました。
cloudflare-workersstripebetter-authnextjsopennextmembershipsubscriptiond1
台本(フルテキスト)
動画の掛け合いを書き起こしたものです。音声を再生しづらい場合はこちらをお読みください。
オープニング
- Cloudflare Workers + better-auth + Stripe でメンバーシップ機能を実装
- Next.js App Router + D1 + OpenNext の構成
- はう
- 今日は Cloudflare Workers に better-auth と Stripe を組み合わせて、個人技術ブログのメンバーシップ機能を実装した記録を紹介します。
- めたん
- どんな構成になっているの?
- はう
- Next.js 15 の App Router を OpenNext で Cloudflare Workers にデプロイして、認証に better-auth、DB に D1、決済に Stripe を使っています。
- めたん
- この組み合わせの日本語の実装記録はほとんどないのよね。
- はう
- そうです。Workers での Stripe 対応や better-auth、OpenNext が安定したのが 2024〜2025 年で、まとまった日本語ログがほぼない状況でした。
- めたん
- 月コストがほぼゼロで運用できるのが魅力ね。
Workers Free plan の 10ms CPU 制限との戦い
- posts-index を 9MB JSON から Worker バンドルに埋め込む形式に変更
- Next.js の Link に prefetch=false を全面適用
- めたん
- Workers Free plan で特に苦労したポイントは何?
- はう
- 1 リクエストあたりの CPU 時間が 10 ミリ秒という制限が想像以上に厳しかったです。
- めたん
- 何が原因で制限を超えたの?
- はう
- 記事メタデータを約 9 MB の JSON ファイルとして置いて、リクエストごとに JSON.parse していたのが原因です。コールド Worker で確実に 10 ミリ秒を超えて 503 エラーになりました。
- めたん
- どう対策したの?
- はう
- posts-index を Worker のバンドルに埋め込む形式に変更しました。gzip で約 200 KB に収まり、JSON.parse のコストがなくなりました。Link は prefetch=false を全面適用して大量のプリフェッチを防いでいます。
Stripe SDK を Workers で動かすための設定
- createFetchHttpClient を明示指定して Node の http 依存を回避
- Webhook 署名検証は constructEventAsync を使用
- めたん
- Stripe の Node SDK をそのまま Workers で使うと問題があるの?
- はう
- はい。Stripe の Node SDK が Node の http モジュールに依存しているので、Workers ではそのままでは動きません。createFetchHttpClient を明示的に指定する必要があります。
- めたん
- Webhook の署名検証も注意が必要ね。
- はう
- 同期版の constructEvent だと WebCrypto が使えず失敗します。constructEventAsync を使う必要があります。
- めたん
- subscription の current_period_end の扱いも変わったのよね。
- はう
- 2025 年以降の Stripe API では、トップレベルから items.data の中に移動しています。SDK の型でも消えているため、unknown 経由でキャストする必要がありました。
SSG とメンバー限定ゲートの両立・DNS 切替の注意
- members_only 記事ページは force-dynamic に設定
- Pages と Workers のカスタムドメインは同一ホスト名を共有できない
- めたん
- メンバー限定記事の判定はどうしているの?
- はう
- 記事ページ全体を force-dynamic に設定して、リクエスト時にセッションを読んでメンバー判定しています。SSG のままだと headers() を呼べずエラーになるためです。
- めたん
- Pages から Workers への切替で詰まったことは?
- はう
- Pages のカスタムドメインが残ったまま wrangler deploy を打つと、hostname already has externally managed DNS records というエラーになりました。Pages 側から先に外す必要があります。
- めたん
- DNS の CNAME レコード削除は CLI で完結できなかったの?
- はう
- wrangler の OAuth トークンには zone:edit 権限が含まれていないため、レコード削除はダッシュボードからの操作になりました。ここだけ CLI 完結できませんでした。
本番化フォームと価格戦略
- 年額 ¥5,000 の単一プランに落ち着いた理由
- Stripe 本番化フォームの日本固有の注意点
- めたん
- 価格はどう設定したの?
- はう
- 最初は月額 500 円でしたが、年額 5000 円の単一プランに落ち着きました。
- めたん
- なぜ年額にしたの?
- はう
- 月課金だと毎月請求イベントが発生して運用が煩雑になります。年額なら解約タイミングが更新時だけに集中して楽なのと、17% 割引のお得感も出せます。
- めたん
- 本番化フォームで注意したことはある?
- はう
- 既存の GitHub Sponsors の Connect アカウントが選択肢に出てくるので、新しいアカウントの作成を選ぶ必要があります。カテゴリはブログおよび記事が実態に近かったです。割賦販売法のセキュリティチェックリストも提出が必要で、Stripe と Cloudflare の組み合わせならほぼ全項目に対応できます。
まとめ
- 月コストほぼゼロ・ベンダーロックイン小・Next.js フル活用
- 公式ドキュメント通りにすぐ動かしたい場合は Vercel + Auth.js が無難
- めたん
- 今日のポイントをまとめてちょうだい。
- はう
- Cloudflare Workers + better-auth + Stripe の組み合わせは、月コストがほぼゼロで、ベンダーロックインが小さく、Next.js の機能をほぼ全部使えます。
- めたん
- ただし詰まりどころも多かったのよね。
- はう
- そうです。CPU 制限、Stripe SDK の Workers 対応、SSG と動的ゲートの両立、DNS 切替の手順など、公式ドキュメントには断片的にしか書かれていない点を中心に整理しました。
- めたん
- 個人運営で Vercel のコストを避けたい場合の有力な選択肢ね。
- はう
- このブログ自体がこの構成で動いているので、実体験に基づいた内容として記録しています。