🍆

Next.js + Supabaseで実装した個人開発のWebアプリをCloudflare Workersにデプロイ時に起きた問題と感想

に公開

個人開発で運用コストを削減する技術構成として、Next.js + Supabaseで開発をし、Cloudflare Pagesにデプロイするという認識が広まりつつあると思います。そんな中、この構成は既に過去の情報となりつつあります。

今後は「Cloudflare Workers」へのデプロイを検討しておきましょう。なんと、Cloudflareが公式的に Pages よりも Workers の利用を推奨しているのです。

Workersが静的アセットの配信とサーバーサイドレンダリングの両方をサポートするようになった今、Workersから始めるべきです。Cloudflare Pages は引き続きサポートされますが、今後、私たちの投資、最適化、機能強化はすべて Workers の改善に専念します。Workersをフルスタックアプリを構築するための最高のプラットフォームとすることを目指し、Pagesで何がうまくいき、何を改善できたか、皆様からのフィードバックを基に構築していきます。

原文

Now that Workers supports both serving static assets and server-side rendering, you should start with Workers. Cloudflare Pages will continue to be supported, but, going forward, all of our investment, optimizations, and feature work will be dedicated to improving Workers. We aim to make Workers the best platform for building full-stack apps, building upon your feedback of what went well with Pages and what we could improve.

Cloudflare 公式ブログから引用

この文章からは、Cloudflare Pages よりも Cloudflare Workers の方が積極的に開発される可能性が高いことを読み取れます。その証拠に現在(2025年5月7日) Pages では Next.js 14 までのサポートに対し、 Workers では最新版の Next.js 15 までサポートされています。

もはや Pages を使うべき理由が失われつつあるのです。

本記事では、将来性のある Cloudflare Workers へ実際にデプロイしたからこそ遭遇した問題点と感想など、細かな情報を共有します。(Next.js 15 + Supabaseを利用。Supabaseでは @supabase/supabase-js および @supabase/ssr をimportしています。)誰かの不安を取り除く記事になれば幸いです。

@opennextjs/cloudflare のv1.0.0は現在(2025年5月7日)ベータ版です。利用は個人開発程度に留めておくべきでしょう。

【AI壁打ち用リンク】NotebookLMに追加すると便利な記事URL

NotebookLMを使ってGeminiに質問しまくってみてください。
僕もこれらの記事をNotebookLMにセットで追加して質問することで多くの疑問が解決できました。

Cloudflare Workersに関する仕様的な記事が中心です。

困ったらとりあえず opennextjs の公式ドキュメントを読めばOK

https://opennext.js.org/cloudflare

基本的な情報はここに全て書かれています。
とりあえず困ったらこのドキュメントを読むようにしましょう!

ビルド処理は opennextjs によって行われる

ドキュメントにも散々書かれているのでご存知かと思います。
情報収集の際の重要なキーワードでもあるので、一応触れておきました。

ちなみに next build でビルド実行したときと全く同じ箇所でビルドエラーが発生したので、差異は無い、もしくは極めて小さいと思います(思いたい)。

Pages へのデプロイにあった特有の問題がない!

https://zenn.dev/masa5714/articles/b43163b7ae3dae

上記の記事でも触れたことがありますが、Cloudflare Pages では、SSRするページに対して

export const runtime = "edge";

を付与する必要がありました。

Edge Runtime で動くことになるため、ややそれ用に記述しなければならず、その影響で他の箇所を修正する必要がありましたが、Cloudflare Workers ではそのような特別な対応が不要でした。

※僕の環境では元々Pagesへのデプロイ前提で開発していたため、何も問題起きなかっただけかもしれません。この辺りは今後様々な人のデプロイ記事を見たいですね。

本当に普通のNext.jsが動いている感ある

Cloudflare Pages へのデプロイでは、Edge Runtimeで動かすので結構クセがあるというか、生のNext.jsじゃない感があったのですが、Cloudflare Workersへのデプロイについては、本物のNext.jsが動いている感があります。とはいえ、「Cloudflare Workersでこれは動くの?」という疑問があるでしょうから、実際に動かした僕からいくつか回答しておきます。

middlewareは動くの?

ちゃんと動いてくれています。サーバー側で認証をしたり、サーバー側でユーザーの認証状況に応じてページ毎のアクセス制限するような処理もちゃんと動いてくれています。(個人的に Pages ではmiddlewareを使ってないので比較しようがありませんが...。)

API Routesも動いてくれるの?

全く問題ないです。

Supabaseもちゃんと動くの?

ちゃんと動いてくれています。クライアントサイドからも、サーバーサイドからも何ら問題もなく動いてくれています。

PWAもちゃんと動くの?

manifest.tsを書けばちゃんとPWAとしてのインストールに対応できます。今回はあくまでサービスまでのショートカット的な役割として導入しただけなので、PUSH通知については実装していません。とりあえずPWAとしてのインストールは問題なかったです。

Intercepting Routesも動いてくれるの?

ちゃんと動いてくれています。何も違和感がないです。(hoge) のようなGroup routesも問題ありませんでした。マジで普通にNext.jsと思って差し支えないでしょう。

Turbopack使って開発してるけど、大丈夫そう?

開発時はTurbopackを使っていました。ビルド時に少し調整が必要でしたが、軽微な調整で済みました。そんなに身構える必要はありません。opennextjsによるビルドでは turbo が現状使えませんから、開発途中でも定期的に npm run build して影響範囲を小さくしておくと良いかもしれません。

修正した内容の例

SVGをコンポーネントとして扱うためにsvgrを使っているのですがこの設定で少し変更が必要でした。
--turbo での開発環境では下記のようにしていましたが、

next.config.ts
    turbo: {
      rules: {
        "*.svg": {
          loaders: [
            {
              loader: "@svgr/webpack",
              options: {
                svgo: false,
              },
            },
          ],
          as: "*.js",
        },
      },
    },
上記をそのままに、これを併記した
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      use: [
        {
          loader: "@svgr/webpack",
          options: {
            svgo: false,
          },
        },
      ],
    });
    return config;
  },

これを併記することでビルドが通るようになりました。
この記述については普通のNext.jsでのビルドでも同様なので Cloudflare Workers 特有の話ではありませんが。

【結構重要】Windows勢はMacやLinux系などの環境を用意する必要ある

下記はWindows 11で開発している僕が直面した問題です。

Windows 11から npm run preview で(ローカル)プレビューを実行しようとすると、ビルド自体は成功してもWebサーバーが立ち上がってくれず、上記なような問題に直面します。

これは恐らく middleware に実装を加えている場合に起きる問題だと思います。
Mac OS や Linux 環境で npm run preview すれば問題なく動いてくれます。
まだまだWeb上に情報が少ない事象なので「意外とOSの違いってあるのかも」と頭の片隅に入れておくと、いつか役に立つと思います。

関連リンク:

https://github.com/opennextjs/opennextjs-cloudflare/issues/494

▼ 公式ドキュメントにもWindowsのサポートはおまけレベル的なことが書かれています。

https://opennext.js.org/cloudflare#windows-support

結構がっつり開発すると無料枠のサイズ上限(3MB)を越えちゃうかも

今回、がっつり開発したところ、無料枠のサイズ上限(3MB)を越えてしまいました。月額5ドルの有料枠(上限10MB)でのデプロイが必要になってしまいました...。無料にこだわりたい人間なので負けた気分ですが、他のデプロイ先と比べても圧倒的に安価なので良しとします。

上記をご覧頂くと、25MB 相当のデータで一見 有料枠の上限も越えているように見えますがデプロイできています。デプロイサイズ上限に影響するのは gzip 圧縮の方のサイズなのです。圧縮前のサイズはデプロイに関係ないので覚えておきましょう!

※当初25MBという数字を見て有料枠でもデプロイ無理じゃん...。って絶望してました。笑

▼ 参考ドキュメント

https://developers.cloudflare.com/workers/platform/limits/#worker-size

ミドルウェアで認証状態を判定するような作りのWebアプリも課金が必要かも?

上記は僕が今回デプロイしたWebアプリのリクエストあたりのCPU使用時間です。ミドルウェアで認証状況に応じて判定を行っています。(例:ログイン状態では /login にアクセスさせずトップページにリダイレクトする。無料プランのユーザーだけ有料コンテンツへのアクセスを拒否するなど。)

CPU使用時間の制限も地味にキツイので、簡易的なデータベースみたいなサイト以外では必然的に有料枠になるんじゃないかと思います。Cloudflare Logsを眺めてみたところ、サーバーサイド認証を挟んでいる影響か分かりませんが、CPU時間という項目が10msを超えるページも珍しくありませんでした。

タイムアウトになって正常な結果を返せないようじゃ意味ないですから、有料枠を使ってもペイできるぐらいのマネタイズ方法は最低限あらかじめ考えておくべきだと思いました。

もしかするとサーバーサイドで認証状態をチェックするような作りのWebアプリでは有料課金しないと厳しいかも?という印象を受けました。

https://developers.cloudflare.com/workers/platform/pricing/#workers

Stripeを動かすには httpClient オプションが必要だった

import Stripe from "stripe";

Stripeによる決済を実装する際、こちらのライブラリを使う方が多いかと思います。

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

このように書いてしまいがちですが、この記述では動いてくれないので注意してください。Cloudflare Workers上で動かすには下記のように httpClient オプションを付与する必要があります。

記述例
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  httpClient: Stripe.createFetchHttpClient(),
});

▼ 参考(これも公式ドキュメントに書かれてる。すごい!)

https://opennext.js.org/cloudflare/howtos/stripeAPI

RSC から Route Handler を await fetch() するのはご法度!

そもそもこんな実装する人なんて居ないはずですが、なぜか自分はやってしまったため、反省として残して書き残しておきます。

どういう実装のことか?

公開ドメインが「example.com」と仮定します。
await fetch("https://example.com/api/hogehoge") のように、サーバーサイドコンポーネントから Route Handler へ自分自身のAPIにリクエストを投げてデータを取得する構造です。

これを Cloudflare Workers にデプロイして動かしてみると、 522 エラーとなってしまいます。このエラーは無限ループが発生している状態です。(ちなみに npm run preview 実行での opennextjs によるビルド&ローカルサーバーでは問題なく動いてくれるのです......。)

ローカルで動いてるから大丈夫!との思い込みは危険です。
Cloudflare Workersでは全然違う動きをする可能性があるので注意しておきましょう。

対策

で、本件を実装するならば以下の2つの対策が考えられます。

  • 関数化して、Route Handlerで呼び出したり、サーバーサイドコンポーネントで呼び出すようにする。(await fetch経由ではなく、関数として内部で呼び出す。)
  • APIサーバーのドメインを切り分ける。(自分自身へリクエストを投げないようにする。)

静的アセットのリクエストは無料とのことだけど、よくわからない。誰か教えてください!

https://developers.cloudflare.com/workers/platform/pricing/#workers

Requests to static assets are free and unlimited. とあります。静的アセットとは画像、Webフォントなどを指すようですが、果たしてどこに設置していれば無料になるかが分かっていません。

また、Next.jsでWebフォントを使うときに import { Inter } from 'next/font/google' などとして使いますが、これも勝手に静的アセットとして扱われているのか?全然分かりません。

リクエストとしてカウントされてるかどうかもどこで確認するか分からず...。とりあえず Cloudflare Logs上では表示されてないので無料枠として扱われてるのだろうかしら?

このあたりご存じの方、コメントにて教えて頂けると嬉しいです!

Discussion