はじめに
こんにちは。前回に引き続き、動物病院向けWebアプリ Vet Clinic Assistant を
開発しているsirocodevです。
前回は「相談データを保存し、一覧画面で表示する」ところまで実装しました。
今回は、見た目まわりとログイン機能を中心に、次の4つを実装しました。
- カラーパレットをCSSに反映
- Supabaseダッシュボードとの連携
- Googleログイン連携機能
- カレンダー機能
完成イメージ
実装した機能
- カラーパレットの反映
- Supabase連携
- Googleログイン連携
- カレンダー機能
対象読者
- Next.js(App Router)を勉強している方
- SupabaseのAuth機能を使ってみたい方
- Google OAuthを個人開発に組み込みたい方
- UIのカラーテーマ設計に興味がある方
開発環境
- Next.js 15
- React
- TypeScript
- Supabase (Database / Storage / Auth)
- Tailwind CSS
1. カラーパレットをCSSに反映
アプリ全体の配色を、指定したブランドカラーに合わせて統一しました。
Tailwindのconfigで色トークンを定義しておくことで、各コンポーネント側の
コードを個別に書き換えることなく、アプリ全体の印象を一度に変更できました。
typescript
colors: {
paper: "#FFF3F6",
surface: "#F0E7D8",
pine: {
500: "#81D8D0", // Brand color
600: "#2D6F68", // Darker
700: "#1F5A53",
},
}
2. Supabaseダッシュボードとの連携
前回作成した consultations テーブルやStorageバケットが、実際に
本番相当の環境として問題なく動作するかを改めて確認しました。
3. Googleログイン連携機能
/login ページに「Continue with Google」ボタンを追加しました。
実装のポイントは以下の通りです。
- Google Cloud ConsoleでOAuthクライアントIDを発行
- SupabaseのAuthentication > Providers(環境によっては
Sign In / Up > Auth Providers)でGoogleを有効化 -
/auth/callbackルートで認可コードをセッションに交換
これにより、/settings ページはログインしたユーザーのみアクセス可能に
なり、プロフィール(表示名)の編集も実際にできるようになりました。
- Googleログイン連携機能
markdown
3. Googleログイン連携機能
/login ページに「Continue with Google」ボタンを追加しました。
実装の中心はこちらです。
```typescript
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: ${window.location.origin}/auth/callback?next=/settings,
},
});
if (error) {
setErrorMessage(error.message);
}
```
signInWithOAuth を呼ぶだけでGoogleログインが開始されるのは、
実装していて素直に「便利だな」と感じた部分でした。
もう一つ地味に効いているのが redirectTo に付けている
next=/settings です。「ログインが終わったらどこに戻すか」を
URLのパラメータとして一緒に渡しておくことで、コールバック側
(/auth/callback)はそのパラメータを見るだけで、適切な画面に
リダイレクトできるようになっています。
エラーが起きた場合も、error.message をそのまま画面に表示する
ようにしました。原因を隠さずに出すことで、開発中の自分自身が
デバッグしやすくなるという副次的なメリットもありました。
3-2. ミドルウェアで「どこまで守るか」を決める
markdown
3-2. ミドルウェアで「どこまで守るか」を決める
Googleログインを実装すると、次に出てくるのが「ログインしていない
ユーザーをどう扱うか」という問題です。
よくあるパターンは、ミドルウェアでアプリ全体をログイン必須にして
しまうことだと思います。ただ今回は、次のように保護対象を絞りました。
```typescript
const PROTECTED_PREFIXES = ["/settings"];
// ...
if (isProtected && !user) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("next", request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
```
/settings(プロフィール編集画面)だけをログイン必須にして、
ダッシュボードや相談一覧など、それ以外のページはこれまで通り
ログインなしでアクセスできるようにしています。
理由は単純で、アプリ全体を急にログイン必須にしてしまうと、
これまで自分で動作確認していた流れが一気に壊れてしまうためです。
「今回お願いされた範囲(プロフィール編集)だけを守る」という
最小限のスコープに留めることで、既存の挙動を壊さずに機能を
追加できました。
完璧な設計というより、「今の開発段階で何を優先すべきか」を
考えた結果の実装、という感じです。
4. カレンダー機能 (수정판)
markdown
4. カレンダー機能
Appointmentsページに、実際の当月カレンダー(本日をハイライト表示)
を追加しました。あわせて、ブラウザの位置情報を使って現在地の天気を
取得する機能も実装しています。
カレンダーの日付計算は、ライブラリを使わずに素のJavaScriptの
Date オブジェクトだけで行いました。
```typescript
const startWeekday = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
```
ポイントは2行目です。new Date(year, month + 1, 0) は
「翌月の0日目」を意味しますが、JavaScriptではこれが自動的に
「今月の最終日」として解釈されます。この仕様を利用することで、
今月が何日まであるかを1行で求めることができました。
1行目の getDay() と合わせれば、「1日が何曜日から始まるか」と
「今月は何日まであるか」の2つが揃うので、あとはこの情報を元に
カレンダーのマス目を並べるだけで実装できました。地味ですが、
知っていると便利なテクニックだと思います。
4. カレンダー機能
Appointmentsページに、実際の当月カレンダー(本日をハイライト表示)を
追加しました。あわせて、ブラウザの位置情報を使って現在地の天気を
取得する機能も実装しています。
実際にハマったこと
一番苦戦したのはSupabaseまわりでした。
Supabaseが新しいAPIキー方式(sb_secret_...)に移行したタイミングと
重なり、次のようなエラーに遭遇しました。
Invalid Compact JWS
原因は、古いJWT形式の service_role キーを前提にしたコードが残って
いたことでした。環境変数名を SUPABASE_SECRET_KEY に統一し、新しい
キー形式に合わせてコードを見直すことで解決しました。
また、しばらく開発を離れていたタイミングで、次のエラーにも遭遇しました。
fetch failed
調べてみると、Supabaseの無料プランは一定期間アクセスがないと
プロジェクトが自動的にPauseされる仕様でした。ダッシュボードから
「Restore project」を実行することで復旧できました。

次にやりたいこと(今回の宿題)
今回で見た目とログインまわりの基盤は整いましたが、まだ手をつけて
いない部分も多く残っています。次回までの宿題として、以下を予定して
います。
- Consultationの編集・キャンセル機能の実装
- お知らせ(ベルアイコン)アラート機能の拡張
- 右上に表示している「Dr. Ana Reyes」表記を、実際にログイン中の
ユーザー情報を使ったリアルタイム表示に変更 - ログインしているアカウントと、相談記録の「Attending Vet」欄との連携
補足
GitHubリポジトリの初期化と、Vercelへの実際のデプロイについては、
分量が多くなりそうなので次回(後編)でまとめて書く予定です。
おわりに
今回はUIの一貫性とログイン機能という、地味ですが土台になる部分を
整備しました。次回はいよいよ、このアプリを実際にインターネット上へ
公開するところまで書いていきたいと思います。
最後まで読んでいただき、ありがとうございました!



