Zenn
🍆

Next.js + Supabase + Cloudflare PagesでWebサービスを開発・デプロイする際に起きた問題と感想

2024/05/04に公開
2

表題の通り、モダンな構成で個人開発としてWebサービスを開発し、Cloudflare Pagesにデプロイしました。

開発した際に発生した問題と感想を共有しておきます。

※Next.jsはApp Routerを使っています。

なぜこの構成で開発したのか?

とにかく運用にかかるお金を0円にしたかったからです。
この構成であればサーバー代がかかることは基本的にありません。金銭的なリスクゼロで気軽に始められるし、なおかつ放置してても痛くない構成です。

▶ Next.js(フロントエンド・バックエンド)
もはや開発の標準フレームワークですので説明を割愛します。

▶ Supabase(データベース)
個人開発レベルであれば無料で使えます。
DBサイズ500MB、月5GBのエグレス(下り方向のデータ転送)を超えなければ無料枠で収まります。認証機能も備わっていますし、PostgreSQLが使えるので開発の幅が広くて便利です。利益が出てから有料化すれば十分に間に合うぐらい余裕のある無料枠なので、金銭的なリスクゼロで運営できるのは最高な環境ですね!

※Supabaseを使う前に見て欲しい記事を書きました!気軽に使うとマズイ部分もあるのでその注意喚起が主な内容です。
https://zenn.dev/masa5714/articles/581ed3b77394ae

▶ Cloudflare Pages(Next.jsのホスティング)
Vercelを使いたいところですが無料プランでは商用利用が許可されていないため、弱小の個人開発勢には不向きです。

そのため、1日あたり10万リクエストまで無料となっており、個人開発レベルであれば無料枠で十分に使えるCloudflare Pagesを採用しています。デプロイ作業も簡単ですし、プレビュー機能(ステージング的な扱いのもの)が使えるのも嬉しいところです。

リクエスト超過してしまっても月5ドル程度で利用できるので怖くないですね。
Cloudflareはマジで慈善団体!


つまり、サーバー代0円でノーリスクなのに爆速なWebサービスが公開できてしまいます。世界中の投資家さんに感謝しながら有り難く使わせて頂きましょう!

0. どんなサービスを作ったのか?

  • スクレイピングでデータを収集している
  • 加工したデータを元にコンテンツを構成している
  • Next.jsのSSRを利用している
  • 500,000ページ以上ある

というものです。
ローカル上で集めたデータを公開しているサービスです。

https://mintbox.jp/

※通常ならGoogle Maps APIを使いそうなサービスですが、無料のAPIを用いて、位置情報から郵便番号へ、キーワードから郵便番号への変換を実現させています。徹底して無料にこだわっています。

※PCのSafariでモーダルが左に寄っちゃう不具合認知してますが修正する気力起きず。スマホユーザーが8割以上だし。

1. Supabase CLIが便利だった

よくこの構成で開発する場合、「Prisma」が使われる傾向にあると思います。
しかし、SupabaseにおいてはPrismaは不要だと思っています。というのも、Supabase CLIがすごく良い感じで、GUIで作成したテーブルなどもマイグレーションファイルに変換できる機能が備わっています。

かなり前の経験なので今はどうなっているか未確認なので聞き流して頂きたいのですが、Prismaを使うことによってSupabaseの不具合に遭遇することが多くありました。この経験からPrismaを使う気には一切なりませんでした。

Supabase CLIだけでかなりスムーズな開発ができます。

▼ PrismaについてSupabaseの中の人「タイラーさん」による見解

https://twitter.com/dshukertjrjp/status/1771725005144908077

2. 【多分】Windows環境だとちょこちょこ問題に遭遇する。(すぐに解決できるレベル)

僕はWindows 11を使っています。
WindowsとSupabaseの相性が若干悪いようでWindows特有の問題に遭遇することがありました。

2-1. TypeScript型の文字コードが不適切だった

supabase gen types typescript で生成した型データの文字コードが不適切で、 npm run build 時にエラーが発生してしまう問題がありました。文字コードを UTF-8 に直すだけで解決します。手作業で都度直すのは面倒だったので prebuild のタイミングで自動変換する仕組みを実装して意識することなくビルドできる環境を手に入れました。

▼解決策の記事を書きました。
https://zenn.dev/masa5714/articles/8303ae09d4e09d

本番環境の Supabase と連携する際に supabase link --ref-project というコマンドを叩くのですが、このコマンドが動いてくれない事象が発生しました。

https://github.com/supabase/supabase/issues/15184

こちらに投稿されている方法で、

$env:SUPABASE_DB_PASSWORD="【ここにDBパスワード】"; supabase link --project-ref 【ここにプロジェクトのref値】

このコマンドで解決できました。

2-3. ローカルでSupabaseを動かすとめちゃくちゃ重かった

これはSupabaseが問題というよりはDocker側の話かもしれませんが、とにかくローカル環境が重かったです。メモリ使用率が99%となり何も作業できないレベルでした。

簡単な設定変更だけで解決しました。

2025年3月17日追記:

今では物理的にマシンを分けて処理しています。物理的にマシンを分けてはいますが、開発体験を損なわないためにリバースプロキシを立てて実現しています。具体的な方法は下記記事に書きましたのでぜひご覧ください。

https://zenn.dev/masa5714/articles/9b343d88008a60

3. メタデータの出力にも工夫が必要だった

これはNext.js寄りのお話になるのですが、メタデータ出力とブラウザ表示で分けて処理されるため、Supabaseへデータ取得が2回リクエストが行われてしまう問題がありました。

コードで見る(こんな感じ)
page.tsx
// 動的にメタデータを出力する
export async function generateMetadata({ params }: { params: { id: string } }) {
  const data = await getPostData(params.id);

  return {
    title: data.title,
    description: data.description
  };
}

// ブラウザ画面に出力する
export default async function MyPage({ params }: { params: { id: string } }) {
  const data = await getPostData(params.id);

  return (
    <>
      <p>{data.title}</p>
    </>
  );
}

どちらも同じデータを使っているのに無駄にリクエストが2回発生しているので削減する必要がありました。そこで import { cache } from "react"; を活用した実装を行いました。

これでキャッシュが効いてくれて無駄なリクエストが発生しなくなりました。

解決策の詳しい内容は下記の記事をご覧ください。
https://zenn.dev/masa5714/articles/f28b7ec738bf77

4. Cloudflare PagesにデプロイするにはEdge Runtimeを有効にする必要があり、やや修正が必要だった

Next.jsをCloudflare Pagesにデプロイするには Edge Runtime を有効にしなければなりません。それに伴う修正がいくつか必要だったので共有します。

4-1. SSRするページに export const runtime = "edge"; をつける

Next.jsでは page.tsxに export const runtime = "edge"; を追記するだけで Edge runtimeが有効になります。SSRを使ってるページではこの記述を追加するだけで解決します。

4-2. サイトマップの生成処理が動かなかった

App Routerにはサイトマップ生成機能が備わっていますが、これが Edge Runtimeで動かなかったため使えませんでした。

今回のサービスでは50万ページ以上あるためサイトマップを分割しなければなりません。(サイトマップは5万個のURLしか含められないため。)また、分割出力したサイトマップのインデックスを出力してくれる仕組みも無いため辛かったです。

ということで、かなり面倒ですがサイトマップを生成する仕組みを独自実装して解決しました。

▼catnoseさんの下記記事の「3. xmlのコードを生成する」箇所をベースに構築しました。
https://zenn.dev/catnose99/articles/c441954a987c24

サイトマップだけで8時間ぐらい溶かした...。(サイトマップの準備は意外と時間かかるかもなので注意してください!)

5. SupabaseのGUI上でCSVインポートを行ったら文字化けしてしまった

SupabaseのGUI(Supabase Studio)には Import data via spreadsheet というCSVファイルからテーブルを作成する便利な機能があります。

しかし、これを使うのはオススメできません。

というのも、43万行ぐらいのデータをインポートしたところ、200件強で文字化けが発生してしまいました。不完全なデータを挿入してしまうことになるので極力使わない方がいいかもしれません。

DBeaverを使ってCSVインポートを行ったところ正常に挿入できました。

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

6. データ移行はDBeaverを使った

本番環境時にはローカル環境で溜め込んだデータを本番環境に移行しなければなりません。Supabase CLIにはそのような仕組みがありませんので、DBeaverを使って移行させました。( supabase db push --linked はマイグレーションファイルの実行のみです。 )

7. データベース使用量は290MBぐらい

Supabaseの無料枠ではデータベース使用量500MBという制限があります。データベースを普段触らない人にはこの規模感が分かりづらいかと思いますので、今回のケースの使用量を共有します。

テーブル数: 4つ
データ量: 約112万レコード(行)
インデックス: 結構張ってる(なんて伝えるのが適切か分からない...。)

1レコードあたりは6カラム程度なのでデータが大きくならずに済んだ部分はありますが、500MBのデータベースは個人開発レベルでは十分すぎるとお考え頂いてよろしいかと思います。

ちなみに僕はSupabaseの課金に到達しにくくするためにStorage機能を使っていません。画像などはGCPのCloud Storageを使っています。ですので、文字列の転送だけで5GBに到達しなければ無料で使えるということですね!

8. ページ数が多すぎてCloudflare Pagesに課金しないとダメそう

完全無料で運用できると考えていましたが、サイトマップに含まれるページ数が多すぎてCloudflare Pageに課金をしないとダメそうです。なぜなら、Googleボットなどがクロールのために大量にリクエストをしてくるからです。

今日だけでも9万リクエストをしてきており、1日上限の10万リクエストに到達しそうです。様子を見て課金をしようと思います。ただ、GCPのCloud Runと比べればかなり安く素晴らしいサーバーを借りられるので全然問題ないですね。

※Supabaseについては文字列が配信されてるだけなので無料枠で済みそうです。

【付録1】個人開発ではPC版を作らない方針にしました

デザインが苦手な僕はPC版のサイトを準備しないことにしました。スマホだけでデザイン辛いのにPCも作るのは無理です。それとリサイズ周りで考慮すべきことが多すぎる割にリターンが無さすぎます。(僕のサイトで計測した限りでは8割以上がスマホユーザーなので...。たった2割のためにスマホのUIのクオリティを下げるのは不適切だと感じています。)

ただし、準備しませんが、一応PCでも見れる状態にはしています。
※横スクロールが必要な要素はドラッグでスクロールできるようにはしています。最低限の考慮だけで済ませられます。

▼ 下記の手法を用いてスマホサイトに対応しています。(どんなデバイスでも崩れにくい。現時点で最強の手法だと考えています。)
https://zenn.dev/masa5714/articles/0a05659cf6e84b

よく見るこれを本当の意味で実現できる手段だと思っています。80%のスマホユーザーにこだわりを提供し、20%程度のPC版ユーザーにだけ「とりあえず動く状態」を提供します。

【付録2】Cloudflare Pageの無料枠はリクエスト上限を超えても直ちにストップすることはない

上記は1日あたりのリクエスト数です。
これほど膨れ上げがっている原因は、ページ数が多い影響でGoogleクローラが大量にリクエストしてきているためです。このように約5万リクエストを超過してしまっていますが、それでもリクエスト制限が直ちにかかるということは無いようです。Cloudflareさん神すぎる。

2024年5月8日: とはいえ、迷惑かけ続けるのも良くないので課金しました。(なぜか請求が4.79ドル🤔)

2024年6月8日 有料課金後、Googleクローラのリクエストが落ち着いたので無料枠にダウングレードしました。ボタンを押すだけで何ら特別な作業も発生せずに無料枠へと戻すことができました。結構気軽にスポット的に有料化できるのも嬉しいですね!

【付録3】Supabaseを使えばフロントエンドだけで実装できると思ってる、そこのあなた!間違いではないけどその認識かなり危険ですよ!

Supabaseと言えばフロントエンドからCRUDできる印象が強いかと思います。この認識自体は間違いではありませんが、特に何も考えずにサービスインしてしまって良いかと言われるとNo!です。

その考えのままだとDB丸見え事件を起こしてしまうかもしれません。
かなり危ないことになるかもしれないので絶対に下記を読んでおいてください。(間違った使い方をしてSupabaseへの要らぬ風評被害を生んで欲しくないので...。)

https://zenn.dev/masa5714/articles/811e75dba7a34b

https://zenn.dev/masa5714/articles/581ed3b77394ae

さいごに

細かいところで困ることがいくつかありましたが、開発体験自体は素晴らしいものでした!
そして何より Supabaseも無料枠で使えるし、Cloudflare Pagesも無料枠で使えるし、Webサービスの公開が無料でできる夢のような環境が嬉しかったです!(一瞬だけ有料枠使う羽目になったのでテキスト打ち消し)

気軽に開発もできて、公開もできるので、ぜひこの構成で開発してみてくださいね。

新しく個人開発始めました。色々問題ありそうだけど気にしない🖐️
https://zenn.dev/masa5714/scraps/3d2349e0f550d3

2025年1月16日追記:
上記の個人開発は後回しにして他のやつ作り始めてしまいました...👼
課金タイプの語学学習サービス作ってます!

2

Discussion

ログインするとコメントできます