見出し画像

AIとサービスを作る #03 ——実装編②:全画面実装からSupabase連携まで

前回のフェーズ0「環境構築」では、Next.jsのプロジェクトを立ち上げてGitHubにコードをアップロードするところまで進みました。今回はその続き、フェーズ1「画面の骨格を作る」とフェーズ2「データをつなぐ」 の記録です。

コードを書き始めたら想像以上にスムーズで、1日でかなりのところまで進みました。ただ途中でデザインの壁にぶつかったり、Supabaseのハマりポイントがあったりと、なかなか濃い一日でした。


📋 今回やったこと

# フェーズ 内容
1️⃣ フェーズ1 全7画面をダミーデータで実装
2️⃣ デザイン改善 カラーシステムを全面見直し
3️⃣ フェーズ2 Supabase Auth・DB・Realtime連携
4️⃣ 仕上げ 画面遷移の整備


📱 フェーズ1:全7画面の骨格を作る

Next.js + Tailwind CSSで、StudyBuddyの画面をすべてダミーデータで実装しました。

🔓 未ログイン層(4画面)

画面 URL 役割 🏠 ランディング / サービス紹介・CTA 📝 新規登録 /register アカウント作成 🔑 ログイン /login 認証 👤 プロフィール設定 /profile-setup 資格・時間帯・頻度の入力

🔒 ログイン済み層(4画面)

画面 URL 役割 🏠 ホーム /home 学習ダッシュボード 📊 進捗記録 /progress タイマー・グラフ 🤝 マッチング /matching パートナー検索・申請 💬 チャット /chat リアルタイムメッセージ

ファイル構成は Next.js の App Router の規約に従い、ルートグループで整理しました。

app/
  page.tsx              ← 🏠 ランディング
  (auth)/               ← 🔓 未ログイン層
    login/page.tsx
    register/page.tsx
    profile-setup/page.tsx
  (app)/                ← 🔒 ログイン済み層
    home/page.tsx
    progress/page.tsx
    matching/page.tsx
    chat/page.tsx
components/
  BottomNav.tsx         ← 🧭 下部ナビゲーション

💡 (auth) や (app) という丸括弧のフォルダは URLに影響しないルートグループ です。ログイン前・後の画面を整理するために使っています。

まず完成したのがランディングページ。サービスの価値を伝える3つの特徴カードと「無料で始める」「ログイン」の2つのCTAボタンを配置しました。


ランディングページ

🎨 デザインの壁:「緑の世界に全振り」問題

画面を作り終えてブラウザで確認したとき、最初のデザインはこんな感じでした。

改善前の状態

背景:濃い緑 / カード:緑 / テキスト:薄い緑 / ボタン:緑 …全部緑。「統一感はあるけど、情報のメリハリが弱い」という典型的な状態でした。

Webデザイナー視点でのフィードバックをもとに、カラーシステムを全面的に見直しました。

🔧 改善ポイント6つ

① 明度差をつけてカード・背景を分離

改善前:背景もカードも似たような緑 → のっぺり
改善後:
  背景  #0F1A14(かなり暗い緑)
  カード #1E2E26(背景より+30%明度)

② テキストを3段階の階層に

役割 色 用途 見出し #FFFFFF 白 タイトル・重要数値 本文 #E8EDEB オフホワイト 説明文 補足 #A7B1AC グレー ラベル・日時

③ 数値を「白×太字×大きめ」で主役に

StudyBuddyは進捗が主役のアプリなので、学習時間などの数字はすべて白・fontWeight800で表示。単位(h・分)は小さくして差をつけました。

④ アクセントカラーを役割ごとに使い分ける

🎨 色 カラーコード 用途 🟢 グリーン #3FAF7F CTAボタン・アクティブ状態のみ 🩵 ミント #7FE7C4 先週比プラス・応援メッセージ 🟡 ゴールド #D9A441 連続達成日数(モチベーション)

⑤ ボーダー・影を「緑→白透過・黒ベース」に

改善前:border: 1px solid #1D9E7540  ← 緑の半透明
改善後:border: 1px solid rgba(255,255,255,0.08)  ← 白の半透明
        box-shadow: 0 2px 16px rgba(0,0,0,0.35)   ← 黒ベースで立体感

⑥ BottomNavアイコンをストローク(線)に

塗りつぶしアイコンから stroke 2px の線アイコンに変えて、アクティブ時だけグリーンに変化するようにしました。

学んだこと: デザインは「色の数を増やす」のではなく「明度差・白の使い方・アクセント1色で整える」のが鍵

改善後のホーム画面です。今週の学習時間・連続達成日数・今週vs先週グラフが並びます。データが蓄積されるほど情報量が増える設計になっています。


ホーム画面

🔗 フェーズ2:Supabaseとつなぐ

画面の骨格が完成したので、いよいよバックエンドを接続します。

🔑 Supabase Auth でログイン実装

Supabase の @supabase/ssr パッケージを使って、Next.js App Router に対応した認証を実装しました。ブラウザ用とサーバー用でクライアントを分けるのがポイントです。

// lib/supabase/client.ts(ブラウザ用)
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

認証ガードは proxy.ts(Next.js 16 から middleware.ts が改名)で実装。未ログインのままホーム画面にアクセスしようとすると、自動でログイン画面にリダイレクトされます。

🔓 未ログイン → /home にアクセス → 🔀 自動リダイレクト → /login
🔒 ログイン済み → /login にアクセス → 🔀 自動リダイレクト → /home

ログイン画面

🗄️ DBテーブルの作成

引き継ぎ資料で設計していた4テーブルを Supabase の SQL Editor で作成しました。

テーブル構成:

テーブル 役割 👤 users プロフィール情報 🤝 matches ペア管理(pending / active / dissolved) 📊 study_logs 学習記録(duration_min・studied_at) 💬 messages チャットメッセージ

-- 学習記録テーブル
CREATE TABLE study_logs (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id      UUID REFERENCES users(id),
  duration_min INTEGER NOT NULL,
  studied_at   TIMESTAMPTZ DEFAULT NOW()
);

あわせて Row Level Security(RLS) も設定。自分のデータしか読み書きできないようにしつつ、マッチング候補の表示やパートナーのログ参照など、必要な例外もポリシーで定義しました。

🛡️ RLSとは: Supabaseの行レベルセキュリティ機能。「誰がどのデータを見られるか」をSQL文で定義できます。これにより、万が一APIキーが漏れても他ユーザーのデータは守られます。

SQL実行後、ダッシュボードのTable Editorで4つのテーブルが正常に作成されているのを確認しました。


Supabaseテーブルエディタ


📂 ビジネスロジックを lib/ に切り出す

フェーズ4でのAPI分離を見越して、ロジックはUIから独立したファイルに書きました。

lib/
  progress.ts   ← ⏱️ 連続日数・週次集計・今週vs先週の計算
  matching.ts   ← 🤝 候補検索・スコアリング・申請処理

💡 なぜ分けるの? ロジックをUIに直接書くと、フェーズ4でAPIサーバーに切り出す際に大改修が必要になります。最初から分けておくことで、将来の作業がずっと楽になります。

進捗記録画面ではタイマーで計測した学習時間をそのまま記録でき、棒グラフで今週vs先週を比較できます。


進捗記録画面

⚡ チャットを Realtime で同期

チャット画面は Supabase Realtime を使って、相手のメッセージがリアルタイムで届くように実装しました。

const channel = supabase
  .channel(`chat:${match.id}`)
  .on('postgres_changes', {
    event: 'INSERT',
    schema: 'public',
    table: 'messages',
    filter: `match_id=eq.${match.id}`,
  }, (payload) => {
    setMessages((prev) => [...prev, payload.new as Message])
  })
  .subscribe()

相手がメッセージを送るとDBにINSERTが走り、それをRealtimeが検知してリアルタイムで画面に表示されます。マッチングが成立するとこの画面でメッセージのやり取りが始まります。現時点ではまだパートナーがいないため、下のような案内画面が表示されます。


チャット画面(空の状態)


⚠️ ハマりポイント:メール確認が必要でログインできない

新規登録してプロフィール設定ページを開くと、「ログインが必要です」というエラーが表示されてしまいました。

原因: Supabaseのメール確認機能がデフォルトでONになっており、登録直後はまだメールを確認していないのでログイン状態にならないのです。

✅ 解決方法:

Supabase ダッシュボード
  → Authentication
    → Sign In / Providers
      → Email の「Confirm email」をOFF

⚠️ 注意: 開発中はOFFで問題ありませんが、本番リリース時には必ずONに戻してメール確認フローを実装してください。


🧭 画面遷移の整備

ひととおり実装が終わったところで、画面遷移の抜け漏れに気づきました。

追加した遷移:

画面 追加した遷移 🔑 ログイン ロゴクリック → 🏠 トップページ 📝 新規登録 ロゴクリック → 🏠 トップページ 👤 プロフィール設定 ◀️「← ログインに戻る」リンク 🏠 ホーム 🚪 右上に「ログアウト」ボタン

特にログアウト手段は最初に実装し忘れていて、ホーム画面から抜け出せない状態になっていました。

💡 こういう「動いてから気づく抜け漏れ」はコードを書き始めないとわからない。設計だけでは見えない問題が、実装すると見えてくる。

マッチング画面では、フィルター(活動時間帯・報告頻度)でパートナー候補を絞り込んで申請できます。


マッチング画面

💭 振り返り

今日は一日でフェーズ1とフェーズ2がほぼ完成しました。設計をきちんと済ませていたおかげで、実装の迷いが少なかったと思います。

✅ うまくいったこと

  • 設計段階でファイル構成を決めていたので、迷わず実装できた

  • ビジネスロジックを最初からlib/に分けたので、コードが読みやすい

📚 学んだこと

  • デザインは「色の数」ではなく「明度差と情報の階層」が大事

  • Supabaseの認証は開発時と本番時で設定が変わる点に注意

  • RLSの設定は面倒だが、セキュリティの土台として最初にやるべき

次回 #04 はフェーズ3「磨いて公開する」に入ります。PWA設定・Webプッシュ通知・Vercelへのデプロイを進めて、いよいよ MVP完成 を目指します。


#StudyBuddy #Next .js #Supabase #個人開発 #AIプロダクト開発

いいなと思ったら応援しよう!