关于 supabase
supabase 是面向 Next.js SaaS 开发的配置指南 skill,适用于需要设置认证、数据库和数据访问控制的开发者。它说明以 Supabase 客户端处理受 RLS 保护的用户数据,并以 Drizzle 和连接池处理服务端数据访问。
主要功能
- 01规划双通道数据访问架构
- 02配置 Supabase SSR 客户端与中间件
- 03设置 Google OAuth 认证流程
- 04使用 Drizzle 配置数据库和迁移
- 05提供 RLS 策略与安全检查清单
- 06说明连接池和成本优化注意事项
适用场景
- 01为 Next.js SaaS 配置 Supabase 认证
- 02通过 Drizzle 连接 Supabase 数据库
- 03为公共 schema 的表启用 RLS
- 04排查连接池或令牌刷新问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dicklesworthstone/ agent_flywheel_clawdbot_skills_and_integrations --skill supabase安装后在 Claude Code 中输入/supabase确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ARCHITECTURE.mdAUTH-SSR.mdCOST-OPTIMIZATION.mdDRIZZLE-SETUP.mdGOOGLE-OAUTH.mdPOOLER-GOTCHAS.mdQUICK-REFERENCE.mdRLS-PATTERNS.mdTROUBLESHOOTING.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
安全规则
- 使用
getClaims(),而不是getSession()— Cookie 可能被伪造;getClaims()每次都会验证 JWT 签名 - 绝不要公开
service_role密钥 — 它会绕过 RLS,只能在后端使用 - 为
public中的每个表启用 RLS — 未启用 RLS 的表可通过 anon 角色访问 - 使用
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