SupabaseをNext.jsポートフォリオに組み込んだ話|FirebaseからDB移行した13時間の記録
ポートフォリオにSupabase(PostgreSQL BaaS)を導入しFirebaseから移行した全記録。RLSの設定ミスで2時間溶かした失敗談と、Next.js・TypeScriptとの連携方法を実際のコードで解説します。
※当サイトはアフィリエイトプログラムに参加しています。記事内のリンクから商品を購入すると、当サイトに報酬が支払われることがあります。詳しくはプライバシーポリシーをご覧ください。
「3月末の土曜日、ポートフォリオを大幅リニューアルしよう」と思い立って始めた作業が、気づいたら深夜2時になっていました。
ここまで読んでくれた人に正直に言うと、Supabaseの導入そのものより、RLSの設定ミスを直すのに2時間溶かしたのが最大の山でした。
(この記事を書いている間にコーヒーが2杯冷めた)
この記事では、FirebaseからSupabaseへの移行を13時間かけてやり遂げた一部始終を書きます。コードも全部貼るので、同じ轍を踏まないために参考にしてみてください。
なぜ今さらFirebaseからSupabaseに乗り換えたか
先に結論を言うと、「SQLを実務レベルで書けるかどうか」が採用に影響すると感じたからです。
もともと僕のポートフォリオはFirebaseのFirestoreを使っていました。データの読み書きは簡単で、認証もGoogleログインをサクッと実装できる。「便利なFirebase」はとても優秀だったんですが、3月に面談したとある会社のCTOに「FirestoreしかやってないとRDBMSの経験が薄く見えることがある」と言われて、そこから気になり始めました。
正直、当時の僕は「え、Firestoreって使いやすいし何がダメなの」と思っていました。でもよく考えたら、業務システムの多くはPostgreSQLやMySQLで動いていて、フロントエンドエンジニアでも「SQLくらいは読める」のが当然視されている部分があります。
SupabaseはPostgreSQLをバックエンドに持つBaaS(Backend as a Service)です。FirebaseのようにNoCodeに近い感覚で使えつつ、中身はちゃんとSQLが使える。「手を動かしながらRDBMSを学ぶ」という名目で乗り換えを決めました。
年度末の忙しい時期に始めたのは完全に僕の判断ミスでしたが、結果的にはやってよかったです。
実際の移行手順:Next.js × TypeScript × Supabaseの組み合わせ
1. Supabaseプロジェクトの作成
supabase.comでアカウントを作って、プロジェクトを新規作成します。
リージョンは「East Asia (Tokyo)」を選べるので、日本向けサービスならここを選ぶのがレイテンシ的にベターです。DBのパスワードはしっかり管理してください(.env.localに書いてGit管理外にするのは当然として)。
2. SDKのインストールと型の自動生成
npm install @supabase/supabase-js
npm install --save-dev supabase
# Supabase CLIでDBの型定義を自動生成
npx supabase gen types typescript --project-id <your-project-id> > types/supabase.ts
ここがポイントなんですが、型定義の自動生成は必ず最初にやっておくことをおすすめします。後からやると、すでに書いたコードを全部型定義に合わせて直すことになります。正直、これを後回しにして1時間無駄にしました。
TypeScriptの型システムをある程度理解していると、この恩恵がより実感できます。
3. クライアントの初期化
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/supabase'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey)
型引数にDatabaseを渡すことで、テーブルへのクエリが型安全になります。from('profiles')と書いた時点で、そのテーブルのカラム名が補完されるのは気持ちいい体験でした。
4. データの読み書き
// ユーザー一覧を取得
const { data, error } = await supabase
.from('profiles')
.select('id, name, bio')
.order('created_at', { ascending: false })
// データを追加
const { data: inserted, error: insertError } = await supabase
.from('profiles')
.insert({ name: 'Souma', bio: 'フロントエンドエンジニア' })
.select()
Firebaseに比べてクエリが直感的に書けるのは好印象でした。order()やfilter()がメソッドチェーンでつながっていく感覚は、REST APIの設計思想に近いものがあります。
ハマったポイント3つ(ここ書くのに30分悩んだ)
① RLSを有効にしたら全データが見えなくなった【2時間の沼】
Supabaseには**Row Level Security(RLS)**という機能があります。要は「どのユーザーがどのデータを見ていいか」を行レベルで制御する仕組みです。
問題は、RLSをデフォルトで有効にすると、ポリシーを設定しない限りデータが全件取れなくなることです。
最初は「データが取れない、バグだ」と30分くらい悩みました。その後も「ポリシーを設定したはずなのになんで見えないんだ」で1時間半。合計で2時間くらいここに費やしました。
-- 全員が閲覧できるポリシーを設定する例
CREATE POLICY "Public profiles are viewable by everyone."
ON profiles FOR SELECT
USING (true);
-- ログインユーザーのみが自分のデータを更新できるポリシー
CREATE POLICY "Users can update own profile."
ON profiles FOR UPDATE
USING (auth.uid() = id);
ちょっと話が逸れるんですが、このRLSという概念、実際の業務システムでも「なぜこのユーザーにはこのデータが見えないんだ」という問題の根本原因になりやすいです。SupabaseでRLSを学んでおくと、業務での権限設計の理解にも繋がります。
SQL・データベースの基礎をある程度知っていれば、もっと早く気づけたポイントでした。
② 型定義ファイルの再生成を忘れる
DBのスキーマを変更したあと、型定義ファイルを再生成しないとTypeScriptが古い型を参照し続けます。これで30分くらい「なんでこの型エラーが消えないんだ」となりました。
スキーマを変えたら必ずnpx supabase gen typesを実行する、をルーティン化するのがおすすめです。
③ 無料枠の制限で焦った
Supbaseの無料枠は7日間非アクティブだとプロジェクトが一時停止されます。ポートフォリオのDBが止まっていて面接中にデモができない、という最悪のシナリオは避けたいところです。
週1回は触るか、GitHub Actionsで定期実行するなどの対策が有効です。僕は簡単なAPIヘルスチェックをActions経由で毎日叩くようにして解決しました。
Supabaseをおすすめしない人・場面
これ、ポジティブな記事でも必ず書くと決めています。「全肯定レビュー」を信用する人はいないので。
こういう人・場面にはFirebaseのほうが向いていると思います:
- 最速でリリースしたい: Firestoreのほうがセットアップが速い。RLSの設定でハマる時間が省ける
- リアルタイム同期がメイン: FirebaseのonSnapshot()はリアルタイム性が高く、Supabaseのrealtimeより安定している印象
- SQLを書く気がない: SupabaseのUIは便利ですが、本質的にはSQLを理解しないと詰まります
- モバイルアプリも作る: FirebaseのFlutter/iOS/Android対応は圧倒的に成熟している
逆に「SQLを学びながらBaaSも使いたい」「ポートフォリオでRDBMSの経験をアピールしたい」という目的なら、Supabaseは最適です。
よくある質問
Q: Supabaseは完全無料で使えますか?
A: 無料プランがあり、個人のポートフォリオや学習用途であれば無料で十分です。ただし、7日間アクティビティがないとプロジェクトが一時停止されるので、定期的にアクセスする仕組みを作っておくことをおすすめします。有料プランは$25/月〜です。本文では触れませんでしたが、Storageも無料枠で1GBまで使えます。
Q: FirebaseからSupabaseへの移行はどのくらい時間がかかりますか?
A: 僕の場合、小規模なポートフォリオ(テーブル3つ、認証あり)で約13時間かかりました。RLSの設定や型定義の整備に想定外の時間がかかります。事前にドキュメントをしっかり読んでから始めると5〜8時間程度に短縮できると思います。
Q: Supabaseの認証(Auth)はFirebaseと比べてどうですか?
A: どちらも似た機能を持っていますが、Supabaseの方が設定がやや複雑です。Googleログインなどのソーシャル認証はどちらも対応しています。Next.jsとの組み合わせには@supabase/auth-helpers-nextjsを使うと認証状態の管理がかなり楽になります。
Q: ポートフォリオにSupabaseを使うと評価が上がりますか?
A: 一概には言えませんが、「RDBMSを実際に触った経験がある」と示せる点は評価されやすいです。ただし使っているだけでなく、RLSや型安全なクエリなど「なぜそう設計したか」を説明できるようにしておくことが重要です。
Q: AWSのRDSやDynamoDBとどう違うの?
A: RDSは自分でサーバーを管理する必要があり、ポートフォリオには過剰です。SupabaseはPostgreSQLをマネージドサービスとして提供しているので、DB自体のサーバー管理が不要です。AWS入門記事でAWSのサービス体系を把握してから比較すると理解しやすいと思います。
まとめ
Supabaseは「Firebaseの便利さ」と「PostgreSQLの本格感」が両立したサービスです。
ポートフォリオにDBを組み込みたいエンジニアには、学習コスト込みでも十分にやる価値があります。特にNext.js × TypeScriptとの相性は良く、型安全なDB操作が実装できます。
もしポートフォリオの作り方で悩んでいる段階であれば、Supabaseを前提にしたDB設計から始めるのも一つの選択肢です。
フロントエンドのデプロイ環境についてはNext.js + Tailwind CSSのセットアップ記事も参考にしてみてください。
【2026年4月追記】Supabase Storage も試しました
記事を書いた後、プロフィール画像のアップロード機能を実装するためにSupabase Storageも触ってみました。
S3と似たAPIで操作でき、画像の公開URL生成やアクセス制御もRLSで管理できます。AWSのS3経験がある人は概念がすんなり入ると思います。画像アップロード実装はまた別の記事でまとめる予定です。