ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Vibe Coding 时代的第一课:用 Next.js + Supabase 从零搭出带登录的 AI 聊天室

Vibe Coding 时代的第一课:用 Next.js + Supabase 从零搭出带登录的 AI 聊天室 Vibe Coding 时代的第一课用 Next.js Supabase 从零搭出带登录的 AI 聊天室【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase当 AI 能在几分钟内生成整页的 React 组件、能按提示词把需求翻译成可运行的代码时编程这件事的门槛被前所未有地拉低了。但 Vibe Coding 有一个残酷的真相AI 擅长的是写代码而不是兜底。数据库迁移、用户认证、权限模型、实时消息推送——这些后端工程里最容易被 AI 糊弄过去的环节恰恰是一个应用能不能真正上线、能不能扛住用户的关键。这正是 Supabase 在 2025-2026 年持续被推上风口的原因。这家把自己定义为 The Postgres development platform 的开源公司先后完成数轮大额融资并收购了数据库公司 Turso估值走向百亿美元量级被大量媒体称作 vibe coding 的默认后端在社区里关于用一个周末开发百万并发应用如何用 Supabase Auth 五分钟接入登录注册的教程长期霸占掘金与 CSDN 的阅读榜。它的核心叙事很清晰给 AI 生成的前端灵感配一个能直接落地的 Postgres 后端。但热度之下也有冷水。安全机构 UpGuard 的研究曾指出有超过 1.6 万个 Supabase 数据库暴露了可读取的表——这不是 Supabase 的缺陷而是默认开放的 Postgres 权限模型与开发者安全意识缺失共同作用的结果。换句话说会用 Supabase 和会用对 Supabase是两回事。本文不写空泛的快速上手而是直接以官方仓库中两个真实示例为蓝本拆解一条完整的进阶路径先用 Supabase Auth 把登录注册接到 Next.js 里再靠 Postgres 的实时订阅把聊天消息广播出去最后用行级安全RLS给每一行数据上锁。三步走完你就拥有一个带登录、实时聊天、权限受控的最小聊天室——这也是绝大多数 AI 应用Agent 会话、Copilot 对话流、协作白板的第一层技术底座。Supabase Auth五分钟接入登录注册先说结论在 Supabase 里用户系统不是一个模块而是数据库自带的一等公民。每个 Supabase 项目初始化时都会自动创建authschema里面躺着auth.users表和整套 JWT 签发、刷新令牌、邮件验证机制。你不需要自己建 users 表、不需要自己写密码哈希、不需要自己管理 session——这些在 examples/user-management/nextjs-user-management 官方示例里都是零新增代码的开箱能力。在 Next.js App Router 下接入的关键是区分两个客户端服务端用 cookie 承载会话浏览器端直接用 anon key 连接。示例中的 lib/supabase/server.ts 展示了标准做法——用supabase/ssr的createServerClient把 Supabase 的 session cookie 与 Next.js 的 cookie store 打通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_PUBLISHABLE_KEY!, { cookies: { getAll() { return cookieStore.getAll() }, setAll(cookiesToSet, _headers) { try { cookiesToSet.forEach(({ name, value, options }) cookieStore.set(name, value, options) ) } catch { // 服务端组件中调用 setAll 可忽略——proxy 会负责刷新会话 } }, }, } ) }对应地浏览器端只需要 lib/supabase/client.ts 里的一行createBrowserClient。接下来登录与注册就变成了两个纯粹的 Server Action——见 app/login/actions.tsuse 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(/error) } revalidatePath(/, layout) redirect(/account) } export async function signup(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.signUp(data) // ... }注意两个细节。第一登录页本身就是一个普通表单靠formAction{login}和formAction{signup}区分两个提交目标全程没有一行手写 fetch。第二如果开启了邮件确认默认在 supabase/config.toml 中enable_confirmations true注册后用户会收到一封带 token 的邮件点击后由 app/auth/confirm/route.ts 接收并调用supabase.auth.verifyOtp()完成验证——这个验证-换 session-重定向的闭环同样是官方模板铺好的。至于登录态的全局同步examples/realtime/nextjs-auth-presence/lib/supabase-context.tsx 给出了一个极简模式用onAuthStateChange订阅事件流SIGNED_IN进聊天室、SIGNED_OUT踢回登录页用户对象永远与真实 session 保持一致。五分钟左右注册、登录、邮件验证、登出全齐——这就是BaaS 吃掉鉴权样板代码的直观体验。Postgres 实时订阅消息广播的两种姿势登录只是入场券聊天室的核心是一条消息发出去所有在线的人立刻看到。这一层在传统架构里通常意味着 WebSocket 网关、连接管理、消息队列——而在 Supabase 里它就是Postgres 数据库自身的变更流。官方的 Slack 克隆示例 examples/slack-clone/nextjs-slack-clone/full-schema.sql 展示了最经典的姿势先建好channels、messages、users三张表然后把它们加入实时发布publicationbegin; drop publication if exists supabase_realtime; create publication supabase_realtime; commit; alter publication supabase_realtime add table public.channels; alter publication supabase_realtime add table public.messages; alter publication supabase_realtime add table public.users;前端这边客户端代码在 lib/Store.js 中通过postgres_changes订阅表的 INSERT / DELETE 事件payload 里的new行就是新消息直接推进 React state 即可const messageListener supabase .channel(public:messages) .on(postgres_changes, { event: INSERT, schema: public, table: messages }, (payload) handleNewMessage(payload.new) ) .subscribe()但如果你读得更深一层会发现官方在 examples/prompts/use-realtime.md一份专为 AI 助手编写的 Realtime 实现规范里给出了更现代的建议新项目优先用broadcast而不是postgres_changes。理由是 postgres_changes 是单线程轮询扩展性受限而broadcast直接走 WebSocket吞吐和延迟控制都更好。官方甚至给了一张决策表表变更通知用数据库触发器 broadcast客户端到客户端的即时消息直接用纯 WebSocket 的 broadcast。这一姿势的最佳实战是 examples/realtime/nextjs-authorization-demo——一个名为 SupaSecureSlack 的授权聊天室。它的核心在 app/protected/page.tsx进入房间前先用supabase.realtime.setAuth(token)把用户 JWT 注入实时连接然后创建私有频道同时监听 broadcast聊天消息和 presence在线成员let newChannel supabase.channel(selectedRoom, { config: { broadcast: { self: true }, private: true, // 私有频道需要认证 RLS 授权 }, }) newChannel .on(broadcast, { event: message }, ({ payload }) addMessage(payload.user_id user?.id, false, payload.message) ) .on(presence, { event: join }, ({ newPresences }) { newPresences.map(({ email }) users.add(email)) setUsers(new Set(users)) }) .subscribe((status, err) { if (status SUBSCRIBED) { newChannel.track({ email: user?.email }) // 上报自己的在线状态 } })发消息则通过channel.send({ type: broadcast, event: message, payload })一行搞定。这份规范还给出了频道命名的硬约束scope:entity:id如room:123:messages、事件名用entity_action如message_created并在所有实现里强制包含退订清理逻辑——这些约定对 AI 生成的代码尤其重要因为它们能让自动写的实时逻辑和人写的实时逻辑长得一样可维护。行级安全策略给 AI 聊天数据上锁如果说前两节解决的是功能这一节解决的是责任。回到开头那个扎眼的数据1.6 万个暴露的 Supabase 数据库。它们的共性问题几乎都是同一个——表建好了、RLS 没开或者开了 RLS 但 anon key匿名 key被赋予了过宽的权限。Supabase 的匿名 key 是可以安全放在前端的但它能读到什么、写到什么完全由 Postgres 的 RLS 策略说了算。这条安全边界就是聊天室和裸奔数据库之间的分界线。RLS 的原理非常优雅每个登录用户会被签发一个 JWT其中携带role authenticated和sub即auth.uid()。数据库在执行每一条 SQL 前都会用策略里的表达式判断当前用户能不能看到/写入这一行。官方用户管理示例的迁移文件 supabase/migrations/20221017024722_init.sql 是教科书级的写法alter table profiles enable row level security; create policy Public profiles are viewable by everyone. on profiles for select using (true); create policy Users can insert their own profile. on profiles for insert with check (auth.uid() id); create policy Users can update own profile. on profiles for update using (auth.uid() id);这段 SQL 的精髓是最后一行的auth.uid() id任何人都看得到资料但只有 id 等于自己的用户才能改自己的资料。同一份迁移里还配套了一个handle_new_user()触发器在auth.users插入后自动为新人建好 profile 行——用户体系与业务表之间的胶水也由数据库自己完成。放到聊天场景Slack 克隆的 full-schema.sql 把这条思路推演到了角色权限层消息只允许作者本人更新/删除频道只允许创建者删除再配合custom_access_token_hook把管理员角色写进 JWT claim实现authorize(messages.delete)这种函数式权限判断。而 SupaSecureSlack 更进一步把 RLS 延伸到了实时连接本身——私有频道的授权不再由客户端自觉决定而是由数据库策略裁决。它针对realtime.messages表Realtime 内部用来转发广播消息的表建立策略只有当rooms_users表里存在当前用户 当前频道名的组合记录时才允许 SELECT读消息和 INSERT发消息create policy authenticated can read broadcast and presence state on realtime.messages as permissive for select to authenticated using ( exists ( select 1 from public.rooms_users where user_id (select auth.uid()) and room_topic realtime.topic() and realtime.messages.extension in (broadcast, presence) ) );这套设计的巧妙之处在于RLS 不是只拦数据库查询它还拦住了 Realtime 的 WebSocket 流量。即便有人拿到前端的 anon key 强行构造一个私有频道订阅也会被realtime.topic()与auth.uid()的组合检查拒之门外。官方在 README 中贴出的对比截图非常直观被授权用户正常进入聊天界面而 RLS 判定无权访问的用户会看到明确的拒绝状态——这正是数据安全由数据库兜底而非由前端自觉的落地形态。结语Vibe Coding 的第一课是学会兜底把三段代码串起来看Vibe Coding 时代的第一课其实不是更快地写出代码而是理解 AI 替你省略的那一层基础设施。Supabase 把鉴权、实时、权限这三块最难做对的后端能力压缩成了一段 SQL 两个客户端方法让 AI 生成的 Next.js 前端可以在一个下午内长出一个可上线的聊天室——这是它成为 vibe coding 默认后端的原因。但同一枚硬币的另一面是当安全边界从自己写的中间件变成了数据库里的一个布尔开关时懂不懂 RLS 的差别就是1.6 万个暴露数据库和私有频道严格授权的差别。所以试着打开 examples/realtime/nextjs-authorization-demo 跑一遍注册一个账号创建一个房间再开一个无权限的账号看看被拒的界面。你会同时体会到 BaaS 的效率红利和 Postgres 权限模型的严谨——这两者合起来才是AI 应用工程师真正该学的第一课。【免费下载链接】supabaseThe Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications.项目地址: https://gitcode.com/GitHub_Trending/supa/supabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进