はじめに — 「バンドメンバーを探す」をデジタルで解決する
「ギターが弾ける人を探している」「ボーカルが抜けてしまった」。バンドをやっている人なら、一度はメンバー探しに苦労した経験があるのではないでしょうか。
BandBridge は、ミュージシャン同士をマッチングするサービスです。プロフィールに演奏スタイルや影響を受けたアーティストを登録し、気になるミュージシャンにスカウトを送り、マッチングしたらチャットで話す。シンプルなコンセプトですが、「マッチングサービス」特有の設計課題がいくつもありました。
この記事では、2026年1月の11日間で BandBridge を形にした開発の舞台裏を、開発日記をもとに振り返ります。元同僚のベテランSEとの共同開発プロジェクトです。
Day 1: MVP骨格を1日で構築する
2026年1月7日、プロジェクト初日。Next.js 16 + Supabase + shadcn/ui の技術スタックで、PMとして設計した機能一覧をClaude Codeに渡し、一気にMVP骨格を構築しました。
Phase 1-4を1日で完了。プロフィール、バンド管理、検索、スカウト、リアルタイムチャット、通報・ブロック機能まで実装
1日で形にできた最大の理由は、Supabaseのフル活用です。認証(Google OAuth)、データベース(PostgreSQL + RLS)、ストレージ(アバター・音源)、リアルタイム(チャット)をすべて Supabase で賄いました。
RLS「デフォルト拒否」設計
マッチングサービスでは「誰が誰のデータを見られるか」の制御が重要です。
RLSは「デフォルト拒否」で設計。まず全アクセスを拒否し、必要な権限だけをポリシーで追加する
「自分のプロフィールだけ編集可能」「マッチングしたユーザーのみチャット閲覧可能」「ブロックしたユーザーの検索結果からの除外」。これらをすべて RLS ポリシーで実装しました。
日本語全文検索 — pg_trgm の活用
ミュージシャンの検索では、「ジャズ ピアノ 東京」のような複合キーワードで検索できる必要があります。
pg_trgm で日本語全文検索を実装。LIKE検索よりも柔軟で、部分一致やタイポ耐性がある
PostgreSQL の pg_trgm 拡張を使うと、トライグラム(3文字の組み合わせ)ベースの類似検索が可能になります。日本語でも動作し、LIKE 検索では難しい「ある程度の揺れ」を許容できます。
セキュリティ監査 — 53件の問題を発見
MVP完成の翌日(1月8日)、PMとしてセキュリティ監査をClaude Codeに依頼しました。
セキュリティ監査で53件の問題を発見。Critical 4件、High 13件。1日で構築したMVPの裏側にはこれだけのリスクが潜んでいた
MVP のスピード重視の開発では、セキュリティが後回しになりがちです。監査結果を見て、「速く作ること」と「安全に作ること」のバランスを改めて考えさせられました。
Critical 4件の内容
- クライアントサイド Supabase の使用: Server Component で
createServerSupabaseClient()を使うべき箇所で、クライアント側のSupabaseを使っていた - フィンガープリントのクライアント生成: 閲覧数カウントのフィンガープリントをクライアントで生成していたため、操作が可能だった
- CRON認証の欠如: 定期実行タスクのエンドポイントに認証がなかった
- デフォルトシークレットの使用: トークン署名にハードコードされたシークレットを使用していた
これらはすべて1月9日に修正しました。
PM-AI 対話: セキュリティ優先度の判断
53件の問題をどう優先付けするか、Claude Codeと議論しました。
crypto.timingSafeEqual() を使ったWebhook検証は High に分類していますが、タイミング攻撃は実装が比較的容易なので、早めの対応を推奨します。実際にこの方針で進め、Critical と High は2日間で対処完了しました。
招待リンクと認証のフロー設計
マッチングサービスでは「バンドへの招待」が重要な機能です。URLを共有するだけでバンドに参加できる仕組みですが、認証との組み合わせに苦労しました。
招待リンクのフロー: 未ログインユーザーが招待URLを開く → ログインページにリダイレクト → ログイン後、元の招待ページに戻る
問題は「ログイン後に元の招待ページに戻す」部分です。OAuth認証ではリダイレクトURLが固定されるため、招待ページのURLを状態として保持する必要がありました。
1月13日の開発日記には、この試行錯誤が記録されています。
招待リンク + 認証のフロー設計: returnTo パラメータをログインURLに付与し、認証完了後にリダイレクト。RLSの複雑さが増すため、ポリシー名を明確に命名する規約を策定
認証状態が必要なページへの「未認証アクセス → ログイン → 元ページ復帰」というフローは、多くのWebサービスで必要になるパターンです。
プロフィール完成度 — ゲーミフィケーションで入力率を上げる
1月12日、プロフィールの入力率を上げるための施策をClaude Codeで実装しました。
プロフィール完成度インジケーターを実装。プログレスバーで現在の完成度を可視化し、未入力項目をチェックリストで表示
マッチングサービスでは、プロフィールの充実度がサービスの価値に直結します。でも「名前と楽器だけ入力して終わり」というユーザーが大半です。
そこで、プロフィール完成度をパーセンテージで表示し、未入力の項目を具体的に示す仕組みを入れました。「影響を受けたアーティスト: 未登録(+5%)」のように、何を入力すればどれだけ完成度が上がるかを見せることで、入力のモチベーションを高めています。
バンドにも同様の完成度チェックリスト(10項目)を用意し、「設立年」「活動拠点」「サウンドサンプル」などの入力を促しています。
リアルタイムチャットの実装
スカウトが承諾されると、自動的にチャットルームが作成される仕組みです。Supabase Realtime を使っています。
スカウト承諾時にチャットルームを自動作成。Supabase Realtime で新メッセージをリアルタイム受信
未読管理には last_read_at カラムを使いました。各ユーザーの最終既読時刻をJSONBカラムに保存し、未読数をリアルタイムで計算します。
1月11日の開発日記に、既読処理の学びが記録されています。
未読バッジの実装: last_read_at で既読管理。チャットルームを開いた時点で
router.refresh()を呼び、Server Component のデータを再取得して未読数を更新
router.refresh() は Next.js App Router 特有のメソッドで、ページ遷移なしに Server Component を再実行してデータを最新化できます。未読数のような「データベースの変更を即座にUIに反映したい」ケースで活躍します。
音源管理とドラッグ&ドロップ
ミュージシャンにとって音源は名刺代わりです。プロフィールに音源をアップロードし、順番を並び替えられるようにしました。
@dnd-kit/sortable でサウンドの並び替えを実装。ドラッグハンドル(GripVertical アイコン)を明示的に配置し、再生ボタンとの干渉を防止
1月17日の開発日記には、ドラッグ&ドロップの設計判断が記録されています。
ドラッグハンドルを明示的に配置。要素全体をドラッグ可能にすると、再生ボタンのクリックと干渉する
音源リストの各アイテムには「再生」「編集」「削除」「ドラッグ」の4つの操作があります。要素全体をドラッグ可能にすると、再生ボタンを押そうとしてドラッグが始まってしまう。そこで GripVertical アイコンを明示的に配置し、「ドラッグはここを掴む」と視覚的に示しました。
認証アーキテクチャの落とし穴
1月15日は、BandBridge 開発で最も大きな技術的問題に直面した日でした。
Supabase Auth + Server Components: getSession() と getUser() の使い分けが重要。Server Component では getSession() を使い、トークンのリフレッシュを Cookie 経由で行う
Server Component から getUser() を呼ぶと、Supabase がトークンをリフレッシュしてもクライアント側の Cookie に反映されず、認証が切れたように見える問題が発生しました。
解決策として getAuthUser() というヘルパー関数を作り、全 Server Component で統一的に使うパターンを確立しました。
PM-AI 対話: 認証ヘルパーの設計
getAuthUser() と requireAuth() の2つのヘルパー関数を作るのが良いです。前者は「ログイン中ならユーザー情報を返す、未ログインなら null」、後者は「未ログインならログインページにリダイレクト」。この2つで全パターンをカバーできます。この認証パターンの整理は、その後の開発効率を大きく向上させました。ドキュメントに「パブリックページ → getAuthUser()」「プライベートページ → requireAuth()」と明記しておくことで、迷いなく実装を進められるようになりました。
N+1 クエリ問題との戦い
1月16日にアクティビティセンターを実装した際、パフォーマンスの問題に直面しました。
N+1クエリ防止: ループ内の個別クエリを
.in()によるバッチ取得に変更。独立したクエリはPromise.allで並列実行
スカウト、応募、メッセージ、いいねの4種類の通知を1ページに表示するアクティビティセンターで、各通知に紐づくユーザー情報を個別に取得していたため、通知100件で100回のクエリが発生していました。
.in() でユーザーIDをバッチ取得し、Promise.all で独立したクエリを並列実行することで解決しました。
まとめ — マッチングサービス開発で学んだこと
11日間の BandBridge 開発を通じて学んだことをまとめます。
- RLSは「デフォルト拒否」で始める: マッチングサービスはアクセス制御が複雑。全拒否から必要な権限だけ開ける設計が安全
- セキュリティ監査はMVP直後に: 1日でMVPを作ると53件のセキュリティ問題が潜む。速さと安全のバランスを意識する
- 招待フローは認証と組み合わせが難しい: 未認証→ログイン→元ページ復帰のフローは、状態管理を丁寧に設計する必要がある
- プロフィール完成度の可視化は効く: パーセンテージと具体的な項目表示で、入力のモチベーションを高められる
- 認証パターンは早期に標準化する: ヘルパー関数とドキュメントで統一しないと、バグの温床になる
マッチングサービスは「人と人をつなぐ」という本質的にはシンプルなコンセプトですが、その実装には認証、アクセス制御、リアルタイム通信、検索、通知といった多くの技術要素が絡み合います。Supabase のようなBaaSを活用することで、これらを個人開発の規模でも実現できることが確認できました。
関連記事:
- Supabase RLS の設計パターン — デフォルト拒否で安全に — RLS設計の詳細
- Supabase Realtime でチャット機能を実装する — チャット実装の技術詳細
- @dnd-kit/sortable でドラッグ&ドロップ並び替えを実装する — D&D実装パターン
Zeronova(ゼロノバ)
Product Manager / AI-Native Builder
Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。