mcpField
S
supabase

supabase

github.com/dicklesworthstone开发7364 次安装8 周前更新
代码仓库

关于 supabase

supabase 是面向 Next.js SaaS 开发的配置指南 skill,适用于需要设置认证、数据库和数据访问控制的开发者。它说明以 Supabase 客户端处理受 RLS 保护的用户数据,并以 Drizzle 和连接池处理服务端数据访问。

主要功能

  1. 01规划双通道数据访问架构
  2. 02配置 Supabase SSR 客户端与中间件
  3. 03设置 Google OAuth 认证流程
  4. 04使用 Drizzle 配置数据库和迁移
  5. 05提供 RLS 策略与安全检查清单
  6. 06说明连接池和成本优化注意事项

适用场景

  1. 01为 Next.js SaaS 配置 Supabase 认证
  2. 02通过 Drizzle 连接 Supabase 数据库
  3. 03为公共 schema 的表启用 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

核心要点: 分为两条路径:用户范围的数据通过 Supabase 客户端访问(由 RLS 强制执行),服务端范围的数据通过 Drizzle 访问(具有特权)。使用 getClaims(),而不是 getSession()。事务池化器要求设置 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 架构
  • 为事件密集型表设置定时汇总任务
  • 为 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
用户随机退出登录中间件未刷新令牌在中间件中使用 getClaims(),而不是 getSession()
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. 使用 getClaims(),而不是 getSession() — Cookie 可能被伪造;getClaims() 每次都会验证 JWT 签名
  2. 绝不要公开 service_role 密钥 — 它会绕过 RLS,只能在后端使用
  3. 为 public 中的每个表启用 RLS — 未启用 RLS 的表可通过 anon 角色访问
  4. 使用 app_metadata 而不是 user_metadata 保存 tenant_id — 用户可以修改 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
双通道架构 + 成本模型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)

评论

支持 Markdown