mcpField
S
supabase

supabase

github.com/dicklesworthstone開発7364 回インストール8 週間前に更新
リポジトリ

supabase について

supabaseは、Supabaseを使ったNext.js SaaSの構築を支援するセットアップガイドスキルです。RLSで保護するユーザーデータにはSupabaseクライアントを、サーバー側のアクセスにはDrizzleとプーラーを使う二系統の構成を説明します。

主な機能

  1. 01二系統のデータアクセス構成の設計
  2. 02Supabase SSRクライアントとミドルウェアの設定
  3. 03Google OAuth認証フローの設定
  4. 04Drizzleによるデータベース接続とマイグレーション
  5. 05RLSポリシーとセキュリティのチェックリスト
  6. 06プーラーとコスト最適化の注意点

活用シーン

  1. 01Next.js SaaS向けSupabase認証の設定
  2. 02Drizzle経由でのSupabaseデータベース接続
  3. 03publicスキーマのテーブルでのRLS有効化
  4. 04プーラーやトークン更新の問題の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dicklesworthstone/agent_flywheel_clawdbot_skills_and_integrations --skill supabase

インストール後、Claude Code で/supabaseを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
ARCHITECTURE.md
AUTH-SSR.md
COST-OPTIMIZATION.md
DRIZZLE-SETUP.md
GOOGLE-OAUTH.md
POOLER-GOTCHAS.md
QUICK-REFERENCE.md
RLS-PATTERNS.md
TROUBLESHOOTING.md

詳細

Next.js SaaS 向け Supabase

重要なポイント: 2 つの経路を使い分けます。ユーザー単位のデータには Supabase クライアント(RLS による強制)を、サーバー単位のデータには Drizzle(特権アクセス)を使用します。getSession() ではなく getClaims() を使用してください。トランザクションプーラーでは prepare: false が必要です。

目次


正確なプロンプト

Set up Supabase for a Next.js SaaS with:
1. Auth: Google OAuth only
2. ORM: Drizzle with Supabase pooler (IPv4)
3. Environment: [Vercel / Cloudflare / local dev]
4. Cost tier: [Free / Pro with Spend Cap / Team]

Follow supabase-guide skill patterns.

アーキテクチャ

┌─────────────────────────────────────────────────────────────────────┐
│                     TWO-LANE DATA ACCESS MODEL                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   LANE A: User-scoped (RLS-enforced)                                │
│   ══════════════════════════════════                                │
│   @supabase/ssr → PostgREST Data API                                │
│   • Browser + Server Components                                     │
│   • JWT validated via getClaims()                                   │
│   • Multi-tenant reads/writes obey RLS "naturally"                  │
│                                                                     │
│   LANE B: Server-scoped (privileged)                                │
│   ══════════════════════════════════                                │
│   Drizzle → postgres.js → Supavisor pooler                          │
│   • Migrations, admin tasks, billing webhooks                       │
│   • Transaction mode (port 6543): prepare: false                    │
│   • Session mode (port 5432): migrations only                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

この使い分けの理由: ブラウザーからのアクセスでは、RLS を適用した Data API を使用します。サーバー/サーバーレスではプーラーを使用します。IPv4 のみの環境(Vercel/GH Actions)では、直接接続(IPv6)を使用できません。


クイックスタート

1. Drizzle + プーラー(トランザクションモード)
// src/db/index.ts
import postgres from "postgres"
import { drizzle } from "drizzle-orm/postgres-js"
import * as schema from "./schema"

const g = globalThis as unknown as { __sql?: ReturnType<typeof postgres> }

export const sql = g.__sql ?? postgres(process.env.DATABASE_URL!, {
  prepare: false,  // REQUIRED for transaction pooler
  max: process.env.NODE_ENV === "production" ? 5 : 1,
})

if (process.env.NODE_ENV !== "production") g.__sql = sql

export const db = drizzle(sql, { schema })
2. Supabase SSR クライアント
// src/lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr"

export function supabaseBrowser() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
  )
}
// src/lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr"
import { cookies } from "next/headers"

export function supabaseServer() {
  const store = cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
    {
      cookies: {
        getAll: () => store.getAll(),
        setAll: (toSet) => {
          try { toSet.forEach(({ name, value, options }) => store.set(name, value, options)) }
          catch {}
        },
      },
    }
  )
}
3. ミドルウェア(トークンの更新)
// middleware.ts
import { createServerClient } from "@supabase/ssr"
import { NextResponse, type NextRequest } from "next/server"

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({ request: { headers: request.headers } })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
    {
      cookies: {
        getAll: () => request.cookies.getAll(),
        setAll: (toSet) => {
          toSet.forEach(({ name, value }) => request.cookies.set(name, value))
          response = NextResponse.next({ request: { headers: request.headers } })
          toSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options))
        },
      },
    }
  )

  await supabase.auth.getClaims()  // NOT getSession()
  return response
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"],
}

環境変数

# Supabase Auth / API
NEXT_PUBLIC_SUPABASE_URL="https://<project_ref>.supabase.co"
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY="sb_publishable_..."  # or anon key during transition
SUPABASE_SERVICE_ROLE_KEY="..."  # server-only, NEVER ship to client

# Postgres: app runtime (transaction pooler, port 6543)
DATABASE_URL="postgresql://postgres.<project_ref>:<PASSWORD>@aws-0-<region>.pooler.supabase.com:6543/postgres?sslmode=require"

# Postgres: migrations (session pooler, port 5432)
DATABASE_URL_MIGRATIONS="postgresql://postgres.<project_ref>:<PASSWORD>@aws-0-<region>.pooler.supabase.com:5432/postgres?sslmode=require"

ユーザー名の形式: postgres.<project_ref> — ピリオドは実際の文字です。Dashboard → Connect ボタンからコピーしてください。


ワークフローチェックリスト

フェーズ 1:プロジェクトのセットアップ
  • 本番用とステージング用の Supabase プロジェクトを作成します(影響範囲を分離します)
  • Connect ボタンから接続文字列をコピーします(推測で入力しないでください)
  • 環境変数を設定します:NEXT_PUBLIC_SUPABASE_URL、NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY、DATABASE_URL、DATABASE_URL_MIGRATIONS
  • Spend Cap を有効にします(Pro プラン)— COST-OPTIMIZATION.md
フェーズ 2:認証(Google のみ)
  • Dashboard → Auth → Providers で Google プロバイダーを有効にします
  • メール/パスワード、マジックリンク、電話番号を無効にします
  • Google Cloud Console にリダイレクト URI を追加します:https://<project_ref>.supabase.co/auth/v1/callback
  • SSR クライアントとミドルウェアをセットアップします — AUTH-SSR.md
  • OAuth コールバックルートを作成します — GOOGLE-OAUTH.md
フェーズ 3:データベース + Drizzle
  • インストールします:bun add drizzle-orm postgres && bun add -d drizzle-kit
  • prepare: false を指定して src/db/index.ts を作成します — DRIZZLE-SETUP.md
  • マイグレーション用のセッションプーラーを参照する drizzle.config.ts を作成します
  • drizzle-kit generate と drizzle-kit migrate を実行します
フェーズ 4:RLS + データモデル
  • auth.users.id をキーとする user_profiles テーブルを作成します
  • public スキーマのすべてのテーブルで RLS を有効にします
  • サインアップ時にプロフィールを自動作成するトリガーを作成します
  • RLS ポリシーで使用する列にインデックスを作成します — RLS-PATTERNS.md
フェーズ 5:コストの最適化
  • 実行時にはトランザクションプーラーを、マイグレーションにはセッションプーラーを使用します
  • Data API を強化します:機密性の高いテーブルを private スキーマに移動します
  • イベント数の多いテーブルに対して、cron による集計を設定します
  • Storage 用に Smart CDN と cache-control を設定します — COST-OPTIMIZATION.md

重大な注意点

症状原因解決方法
ENETUNREACH / network unreachable直接接続が IPv6プーラーの接続文字列を使用します
prepared statement does not existトランザクションプーラーとプリペアドステートメントの併用postgres.js で prepare: false を設定します
Tenant or user not foundプーラーのホストまたはユーザー名の形式が間違っているConnect ダイアログから正確な文字列をコピーし、ユーザー名に .project_ref を含めます
ユーザーがランダムにログアウトされるミドルウェアでトークンが更新されていないミドルウェアでは getSession() ではなく getClaims() を使用します
RLS ポリシーがアクセスをブロックしないテーブルで enable row level security が設定されていないALTER TABLE x ENABLE ROW LEVEL SECURITY を実行します
RLS を使ったクエリが遅いRLS のフィルター列にインデックスがないuser_id、tenant_id 列にインデックスを追加します
想定外の請求Spend Cap が無効で、エグレスがキャッシュされていないSpend Cap を有効にし、Smart CDN と高い cache-control を使用します

トラブルシューティングの詳細:TROUBLESHOOTING.md


セキュリティルール

  1. getSession()ではなくgetClaims()を使用する — Cookieは偽装される可能性があるため、getClaims()は毎回JWT署名を検証します
  2. service_roleキーを絶対に公開しない — RLSを回避するため、バックエンド専用です
  3. public内のすべてのテーブルでRLSを有効にする — RLSがないテーブルにはanonロールでアクセスできます
  4. tenant_idにはuser_metadataではなくapp_metadataを使用する — user_metadataはユーザーが変更できます

クイックコマンド

# Supabase CLI
supabase login
supabase link --project-ref $PROJECT_REF
supabase db pull                        # Pull remote schema
supabase migration new <name>           # Create migration
supabase db push                        # Apply migrations

# Drizzle
drizzle-kit generate                    # Generate migrations
drizzle-kit migrate                     # Apply migrations
drizzle-kit studio                      # Visual browser

# Secrets (Edge Functions)
supabase secrets set --env-file .env    # Push secrets
supabase secrets list                   # Verify

AGENTS.mdの説明

プロジェクトのAGENTS.mdにコピーしてください:

### Supabase

Supabase CLI is installed. Project: `<PROJECT_REF>`.

Auth:

\`\`\`bash
supabase login
supabase link --project-ref $PROJECT_REF
\`\`\`

Database:

\`\`\`bash
supabase db pull           # Pull schema to migrations
supabase migration new <name>
supabase db push           # Apply migrations
\`\`\`

Connection strings:
- **Runtime (Drizzle):** Transaction pooler (port 6543), `prepare: false`
- **Migrations:** Session pooler (port 5432)

Copy exact connection string from Dashboard → Connect button.

参考資料

トピック参考資料
コピー&ペースト用のパターンQUICK-REFERENCE.md
2レーンアーキテクチャ+コストモデルARCHITECTURE.md
Drizzle+プーラーのセットアップDRIZZLE-SETUP.md
getClaims()を使ったNext.js SSR認証AUTH-SSR.md
Google OAuthのセットアップGOOGLE-OAUTH.md
SaaS向けRLSパターンRLS-PATTERNS.md
プーラーの注意点(IPv4/IPv6、モード)POOLER-GOTCHAS.md
コスト最適化(Spend Cap、cron)COST-OPTIMIZATION.md
トラブルシューティングガイドTROUBLESHOOTING.md

検証

# After setup, verify:
# 1. Google OAuth flow works (login → callback → dashboard)
# 2. Drizzle can connect: bun run -e "import { db } from './src/db'; console.log('OK')"
# 3. RLS blocks unauthorized access via Data API
# 4. Middleware refreshes tokens (check cookies in DevTools)

コメント