根据 Supabase 官方 2026 年 Q1 数据,其注册开发者已突破 200 万,GitHub Star 超过 75K,月活跃项目数同比增长 180%。与 Firebase 的黑盒架构不同,Supabase 基于开源的 PostgreSQL 构建,意味着你拥有完整的数据所有权、可本地开发、可自行部署。如果你正在寻找一个既能快速原型验证、又能支撑生产级应用的全栈平台,Supabase 是 2026 年最值得关注的选择。
Supabase 不是一个单一产品,而是一组开源工具的组合。它的核心架构由五个模块组成:Database(PostgreSQL + PostgREST 自动 REST API)、Auth(基于 GoTrue 的认证服务)、Storage(基于 S3 协议的文件存储)、Edge Functions(基于 Deno 的服务端函数)、Realtime(基于 PostgreSQL WAL 的实时推送)。这些模块各自独立运行,通过 Kong 网关统一暴露 API,形成了一个完整的后端即服务平台。
与传统的「先搭后端、再接数据库、再加认证」的开发流程相比,Supabase 让你可以在 5 分钟内拥有一个带认证、数据库、文件存储和实时功能的完整后端。这不是偷懒,而是把精力集中在业务逻辑上,而不是基础设施搭建上。
🏗️ 一、Supabase 架构核心与本地开发环境
1.1 为什么选 Supabase 而非 Firebase?
很多开发者在选择 BaaS(Backend as a Service)时会在 Firebase 和 Supabase 之间纠结。两者的核心差异不在于功能覆盖度,而在于架构哲学。
| 维度 | Supabase | Firebase |
|---|---|---|
| 底层数据库 | PostgreSQL(关系型) | Firestore(文档型) |
| 数据所有权 | ✅ 完全控制,可自托管 | ❌ 数据锁死在 Google |
| SQL 查询 | ✅ 原生 SQL + PostgREST | ❌ 专有查询语法 |
| 实时订阅 | ✅ 基于 PostgreSQL WAL | ✅ 原生支持 |
| 本地开发 | ✅ Docker 完整模拟 | ⚠️ 模拟器功能有限 |
| Edge Functions | ✅ Deno 运行时 | ✅ Cloud Functions |
| 免费额度 | 500MB 数据库 + 1GB 存储 | 1GB Firestore + 5GB 存储 |
| 开源 | ✅ Apache 2.0 | ❌ 闭源 |
⚡ **关键结论:**如果你的项目需要复杂查询(JOIN、聚合、全文搜索)、数据可移植性、或已有的 SQL 经验,Supabase 是更自然的选择。如果你需要极简的 key-value 存储和最快速的原型,Firebase 仍有优势。
1.2 本地开发环境搭建
Supabase 的最大优势之一是完整的本地开发支持。整个平台通过 Docker 运行,你可以在断网环境下开发。
# 安装 Supabase CLI
npm install -g supabase
# 在项目根目录初始化
cd my-project
supabase init
# 启动本地 Supabase(需要 Docker)
supabase start
启动后你会看到类似输出:
API URL: http://127.0.0.1:54321
DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
Studio URL: http://127.0.0.1:54323
Inbucket URL: http://127.0.0.1:54324
JWT secret: super-secret-jwt-token-with-at-least-32-characters
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
💡 提示:
anon key是前端使用的公开密钥,配合 RLS(Row Level Security)保证安全。service_role key是后端管理员密钥,永远不要暴露到前端代码中。
1.3 数据库迁移工作流
Supabase 使用迁移文件管理数据库 Schema 变更,这比直接在控制台操作要可靠得多。
# 创建新的迁移文件
supabase migration new create_users_table
# 编辑 supabase/migrations/xxxx_create_users_table.sql
-- 创建用户资料表
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
display_name TEXT,
avatar_url TEXT,
bio TEXT DEFAULT '',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 自动在用户注册时创建 profile
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, username)
VALUES (NEW.id, NEW.raw_user_meta_data ->> 'username');
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
-- 启用 RLS
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
# 应用迁移到本地数据库
supabase db reset
# 推送到远程 Supabase 项目
supabase db push
📌 **记住:**永远使用迁移文件管理 Schema,而不是直接在 Dashboard 操作。迁移文件可以版本控制、可以回滚、可以在多个环境(开发/测试/生产)一致地应用。
🔐 二、Row Level Security:数据库层面的权限控制
2.1 RLS 的核心理念
Row Level Security(RLS)是 Supabase 安全模型的基石。它的核心思想是:每一行数据都有自己的访问规则,直接在数据库层面执行,绕过应用层代码。
这意味着即使有人拿到了你的 anon key,也无法通过 API 访问到不属于他的数据——因为 RLS 策略会在 PostgreSQL 层面拦截非法查询。
-- 策略 1:所有人可以查看公开的 profile
CREATE POLICY "Profiles are viewable by everyone"
ON public.profiles
FOR SELECT
USING (true);
-- 策略 2:用户只能更新自己的 profile
CREATE POLICY "Users can update own profile"
ON public.profiles
FOR UPDATE
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
-- 策略 3:用户只能删除自己的 profile
CREATE POLICY "Users can delete own profile"
ON public.profiles
FOR DELETE
USING (auth.uid() = id);
2.2 复杂 RLS 策略实战
实际项目中的 RLS 策略往往比简单的 auth.uid() = id 复杂得多。以下是一个多角色、多租户的博客系统示例:
-- 文章表
CREATE TABLE public.posts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
author_id UUID REFERENCES public.profiles(id) NOT NULL,
org_id UUID REFERENCES public.organizations(id) NOT NULL,
title TEXT NOT NULL,
content TEXT,
status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'review', 'published')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
-- 查看策略:已发布文章所有人可见,草稿和审核中只有作者和同组织成员可见
CREATE POLICY "Published posts are public"
ON public.posts FOR SELECT
USING (
status = 'published'
OR author_id = auth.uid()
OR org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid()
)
);
-- 创建策略:只有组织成员可以发布文章
CREATE POLICY "Org members can create posts"
ON public.posts FOR INSERT
WITH CHECK (
org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid()
AND role IN ('author', 'admin')
)
);
-- 更新策略:作者可以编辑草稿,管理员可以编辑任何状态
CREATE POLICY "Authors can update own drafts"
ON public.posts FOR UPDATE
USING (
(author_id = auth.uid() AND status = 'draft')
OR org_id IN (
SELECT org_id FROM public.org_members
WHERE user_id = auth.uid() AND role = 'admin'
)
);
⚠️ **警告:**RLS 默认拒绝所有访问。如果你启用了 RLS 但忘记添加策略,所有查询都会返回空结果——这是新手最常踩的坑。调试时先用
SELECT * FROM pg_policies WHERE tablename = 'your_table'检查策略是否正确。
2.3 RLS 性能优化
RLS 策略中的子查询会在每次查询时执行,如果不注意优化,会成为性能瓶颈。
-- ❌ 避免:每次查询都做全表扫描的 RLS 策略
CREATE POLICY "Bad policy" ON public.posts FOR SELECT
USING (
org_id IN (
SELECT org_id FROM public.org_members -- 每行都执行一次
WHERE user_id = auth.uid()
)
);
-- ✅ 推荐:使用函数缓存 + 索引优化
CREATE OR REPLACE FUNCTION public.get_user_org_ids()
RETURNS SETOF UUID AS $$
SELECT org_id FROM public.org_members WHERE user_id = auth.uid();
$$ LANGUAGE sql SECURITY DEFINER STABLE;
-- 确保有索引
CREATE INDEX idx_org_members_user_id ON public.org_members(user_id);
-- 使用函数的优化策略
CREATE POLICY "Optimized policy" ON public.posts FOR SELECT
USING (org_id IN (SELECT public.get_user_org_ids()));
🚀 三、Auth 认证系统与 Next.js 集成
3.1 多种认证方式
Supabase Auth 支持邮箱密码、魔法链接、社交登录、手机验证码等多种方式。以下是在 Next.js 中的完整集成:
// lib/supabase/client.ts — 客户端 Supabase 实例
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
// lib/supabase/server.ts — 服务端 Supabase 实例
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options)
})
},
},
}
)
}
// app/login/actions.ts — 登录 Server Action
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from '@/lib/supabase/server'
export async function login(formData: FormData) {
const supabase = await createClient()
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signInWithPassword(data)
if (error) {
redirect('/login?error=' + encodeURIComponent(error.message))
}
revalidatePath('/', 'layout')
redirect('/dashboard')
}
export async function signup(formData: FormData) {
const supabase = await createClient()
const { error } = await supabase.auth.signUp({
email: formData.get('email') as string,
password: formData.get('password') as string,
options: {
data: {
username: formData.get('username') as string,
}
}
})
if (error) {
redirect('/signup?error=' + encodeURIComponent(error.message))
}
redirect('/signup?success=1')
}
3.2 社交登录(GitHub OAuth)
// app/login/actions.ts — 新增 GitHub 登录
export async function loginWithGitHub() {
const supabase = await createClient()
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
},
})
if (data.url) {
redirect(data.url)
}
}
// app/auth/callback/route.ts — OAuth 回调处理
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
if (code) {
const supabase = await createClient()
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}/dashboard`)
}
}
return NextResponse.redirect(`${origin}/login?error=auth_failed`)
}
💡 **提示:**Supabase 的 OAuth 回调需要在 Dashboard → Authentication → URL Configuration 中配置
Redirect URLs。本地开发时需要添加http://localhost:3000/auth/callback。
📡 四、Edge Functions 与实时订阅
4.1 Edge Functions 实战
Supabase Edge Functions 基于 Deno 运行时,部署在全球边缘节点,适合处理 Webhook、定时任务、AI 调用等场景。
// supabase/functions/process-payment/index.ts
import { serve } from 'https://deno.land/std@0.208.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
// CORS 处理
if (req.method === 'OPTIONS') {
return new Response('ok', {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
},
})
}
try {
const { order_id } = await req.json()
// 使用 service_role key 绕过 RLS
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// 查询订单
const { data: order, error } = await supabase
.from('orders')
.select('*, profiles(*)')
.eq('id', order_id)
.single()
if (error || !order) {
return new Response(
JSON.stringify({ error: 'Order not found' }),
{ status: 404, headers: { 'Content-Type': 'application/json' } }
)
}
// 模拟支付处理
const paymentResult = {
success: true,
transaction_id: crypto.randomUUID(),
amount: order.total_amount,
}
// 更新订单状态
await supabase
.from('orders')
.update({
status: 'paid',
paid_at: new Date().toISOString(),
transaction_id: paymentResult.transaction_id,
})
.eq('id', order_id)
return new Response(
JSON.stringify(paymentResult),
{ headers: { 'Content-Type': 'application/json' } }
)
} catch (err) {
return new Response(
JSON.stringify({ error: 'Internal server error' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
)
}
})
# 部署 Edge Function
supabase functions deploy process-payment
# 设置环境变量
supabase secrets set STRIPE_SECRET_KEY=sk_test_xxx
4.2 实时订阅
Supabase 的实时功能基于 PostgreSQL 的 WAL(Write-Ahead Log),可以在数据变更时即时推送通知。
// 实时订阅订单状态变更
import { createClient } from '@/lib/supabase/client'
const supabase = createClient()
// 监听特定用户的订单变更
const channel = supabase
.channel('order-updates')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'orders',
filter: `user_id=eq.${userId}`,
},
(payload) => {
console.log('订单状态变更:', payload.new)
// payload.new 包含更新后的完整行数据
if (payload.new.status === 'paid') {
showSuccessNotification(payload.new)
}
}
)
.subscribe()
// 组件卸载时取消订阅
// supabase.removeChannel(channel)
// 实时广播:协作编辑场景
const channel = supabase
.channel('document-collab')
.on('broadcast', { event: 'cursor-move' }, (payload) => {
// 收到其他用户的光标位置
updateRemoteCursor(payload.payload)
})
.on('presence', { event: 'sync' }, () => {
// 获取当前在线用户列表
const state = channel.presenceState()
updateOnlineUsers(state)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// 注册自己的在线状态
await channel.track({
user_id: currentUser.id,
username: currentUser.name,
online_at: new Date().toISOString(),
})
}
})
⚠️ **警告:**实时订阅会占用 Supabase 的连接数。免费计划限制 200 个并发连接,Pro 计划 500 个。务必在组件卸载时调用
removeChannel(),否则会导致连接泄漏。
📊 五、存储与 AI 集成
5.1 对象存储
// 上传用户头像
async function uploadAvatar(file: File): Promise<string> {
const supabase = createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) throw new Error('Not authenticated')
const filePath = `${user.id}/${crypto.randomUUID()}.${file.name.split('.').pop()}`
const { error } = await supabase.storage
.from('avatars')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false,
})
if (error) throw error
// 获取公开 URL
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(filePath)
// 更新 profile
await supabase
.from('profiles')
.update({ avatar_url: publicUrl })
.eq('id', user.id)
return publicUrl
}
5.2 存储桶安全策略
-- 存储桶的 RLS 策略
-- 只有头像拥有者可以删除
CREATE POLICY "Avatar owners can delete"
ON storage.objects
FOR DELETE
USING (
bucket_id = 'avatars'
AND auth.uid()::text = (storage.foldername(name))[1]
);
-- 头像公开可读
CREATE POLICY "Avatars are publicly accessible"
ON storage.objects
FOR SELECT
USING (bucket_id = 'avatars');
5.3 pgvector 向量搜索集成
Supabase 内置了 pgvector 扩展,可以直接在 PostgreSQL 中进行向量相似性搜索,无需额外的向量数据库。
-- 启用 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;
-- 创建文档嵌入表
CREATE TABLE public.documents (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
content TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
embedding VECTOR(1536), -- OpenAI text-embedding-3-small 维度
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 创建 HNSW 索引(比 IVFFlat 更适合小到中型数据集)
CREATE INDEX ON public.documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- 相似性搜索函数
CREATE OR REPLACE FUNCTION match_documents(
query_embedding VECTOR(1536),
match_count INT DEFAULT 10,
filter JSONB DEFAULT '{}'
)
RETURNS TABLE (
id UUID,
content TEXT,
metadata JSONB,
similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
d.id,
d.content,
d.metadata,
1 - (d.embedding <=> query_embedding) AS similarity
FROM public.documents d
WHERE d.metadata @> filter
ORDER BY d.embedding <=> query_embedding
LIMIT match_count;
END;
$$;
// 在 Next.js 中调用向量搜索
import OpenAI from 'openai'
import { createClient } from '@/lib/supabase/server'
const openai = new OpenAI()
async function searchDocuments(query: string, filter = {}) {
const supabase = await createClient()
// 生成查询向量
const embedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: query,
})
// 搜索相似文档
const { data, error } = await supabase
.rpc('match_documents', {
query_embedding: embedding.data[0].embedding,
match_count: 5,
filter: filter,
})
return data
}
💡 **提示:**Supabase 的 pgvector 与 RLS 完全兼容。你可以为不同用户设置不同的文档访问权限,向量搜索会自动过滤无权访问的数据。
⚠️ 六、生产环境避坑指南
在实际项目中使用 Supabase,以下几个坑点需要特别注意:
6.1 连接池管理
Supabase 的直接连接(Port 5432)每个请求创建一个 PostgreSQL 连接,在 Serverless 环境中会迅速耗尽连接数。
// ❌ 避免:Serverless 函数使用直接连接
const supabase = createClient(
'https://xxx.supabase.co',
'key',
{ db: { host: 'db.xxx.supabase.co', port: 5432 } } // 直接连接
)
// ✅ 推荐:使用 Supavisor 连接池(Port 6543)
const supabase = createClient(
'https://xxx.supabase.co',
'key',
{
db: {
host: 'aws-0-ap-northeast-1.pooler.supabase.com',
port: 6543,
}
}
)
6.2 N+1 查询陷阱
Supabase 的 select() 嵌套查询很方便,但不注意会产生 N+1 问题。
// ❌ 避免:循环中单独查询
for (const post of posts) {
const { data: author } = await supabase
.from('profiles')
.select('username, avatar_url')
.eq('id', post.author_id)
.single()
post.author = author
}
// ✅ 推荐:使用嵌套 select 一次查询
const { data: posts } = await supabase
.from('posts')
.select(`
*,
author:profiles(username, avatar_url),
comments(count)
`)
.eq('status', 'published')
.order('created_at', { ascending: false })
.limit(20)
6.3 RLS 调试技巧
-- 检查当前用户的 JWT claims
SELECT auth.uid(), auth.role(), auth.jwt();
-- 测试 RLS 策略(以特定用户身份执行)
SET request.jwt.claims = '{"sub": "user-uuid-here", "role": "authenticated"}';
SET role = 'authenticated';
SELECT * FROM public.posts; -- 查看该用户能看到什么
RESET role;
6.4 TypeScript 类型自动生成
Supabase CLI 可以从数据库 Schema 自动生成 TypeScript 类型定义,这意味着你的前端代码和数据库结构永远保持同步。
# 从远程数据库生成类型
supabase gen types typescript --project-id your-project-id > lib/database.types.ts
# 从本地数据库生成(开发时使用)
supabase gen types typescript --local > lib/database.types.ts
生成的类型文件可以直接传递给 Supabase 客户端,获得完整的类型提示:
// lib/supabase/client.ts — 带类型的 Supabase 客户端
import { createBrowserClient } from '@supabase/ssr'
import { Database } from '@/lib/database.types'
export function createClient() {
return createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
// 使用时获得完整的类型推断
const supabase = createClient()
const { data } = await supabase
.from('posts') // IDE 自动补全表名
.select('title, status, author:profiles(username)') // 自动补全字段名
// data 的类型自动推断为:
// { title: string; status: "draft" | "review" | "published"; author: { username: string } }[]
📌 **记住:**每次运行
supabase db push或修改 Schema 后,都应该重新生成类型文件。可以在package.json中添加"db:types": "supabase gen types typescript --local > lib/database.types.ts"作为快捷命令。
6.5 数据库函数(RPC)调用
对于复杂的业务逻辑,直接在 PostgreSQL 中编写函数比在应用层多次查询更高效。
-- 创建一个带权限检查的原子操作函数
CREATE OR REPLACE FUNCTION public.transfer_credits(
from_user UUID,
to_user UUID,
amount INT
)
RETURNS JSONB AS $$
DECLARE
from_balance INT;
result JSONB;
BEGIN
-- 检查发送方余额(带行锁)
SELECT credits INTO from_balance
FROM public.profiles
WHERE id = from_user
FOR UPDATE;
IF from_balance < amount THEN
RETURN jsonb_build_object('success', false, 'error', '余额不足');
END IF;
-- 原子转账
UPDATE public.profiles SET credits = credits - amount WHERE id = from_user;
UPDATE public.profiles SET credits = credits + amount WHERE id = to_user;
-- 记录交易
INSERT INTO public.credit_transactions (from_user, to_user, amount)
VALUES (from_user, to_user, amount);
RETURN jsonb_build_object('success', true, 'new_balance', from_balance - amount);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
// 在前端调用 RPC 函数
const { data, error } = await supabase
.rpc('transfer_credits', {
from_user: currentUser.id,
to_user: targetUserId,
amount: 100,
})
if (data.success) {
console.log('转账成功,新余额:', data.new_balance)
} else {
console.error('转账失败:', data.error)
}
⚠️ **警告:**使用
SECURITY DEFINER的函数会以函数创建者的权限执行,绕过 RLS。务必在函数内部手动实现权限检查,否则可能造成安全漏洞。只在确实需要跨表原子操作时使用SECURITY DEFINER。
🎯 总结与最佳实践
Supabase 在 2026 年已经成为全栈开发的主流选择之一。它的核心优势在于:用开源的 PostgreSQL 打底,用 RLS 做安全,用 Edge Functions 做逻辑,用实时订阅做交互——形成了一个完整的全栈能力闭环。
使用 Supabase 的关键建议:
- ✅ 从迁移文件开始:用
supabase migration管理所有 Schema 变更,而不是直接在 Dashboard 操作 - ✅ RLS 优先设计:在写应用代码之前先设计 RLS 策略,确保数据安全是数据库层面的
- ✅ 善用嵌套查询:Supabase 的
select()支持关系嵌套,可以一次查询获取关联数据 - ✅ 使用连接池:在 Serverless 环境中必须使用 Supavisor(Port 6543),不要用直接连接
- ✅ 自动生成类型:用
supabase gen types typescript保持前后端类型同步 - ✅ 善用 RPC 函数:复杂业务逻辑下沉到 PostgreSQL 函数,减少网络往返
- ❌ 不要把 service_role key 暴露到前端:这个密钥可以绕过 RLS,泄露等于裸奔
- ❌ 不要忽视实时连接数:每次订阅都占用一个连接,必须在卸载时清理
- ❌ 不要在 RLS 策略中写复杂子查询:用
SECURITY DEFINER函数封装,避免性能问题
相关工具和资源:
| 工具 | 说明 | 链接 |
|---|---|---|
| Supabase CLI | 本地开发与迁移管理 | npm: supabase |
| Supabase Dashboard | 在线管理控制台 | app.supabase.com |
| @supabase/ssr | Next.js/Nuxt SSR 集成 | npm: @supabase/ssr |
| pgvector | 向量相似性搜索 | supabase.com/docs/guides/database/extensions/pgvector |
| Supabase Realtime | 实时订阅文档 | supabase.com/docs/guides/realtime |
| PostgREST | 自动 REST API 引擎 | postgrest.org |
| supabase-ui | 官方 UI 组件库 | github.com/supabase/ui |